source: trunk/lib/Configuration.cc @ 1144

Last change on this file since 1144 was 1144, checked in by Peter Johansson, 13 years ago

refs #326. Let SVNproperty retrieve info from config file

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 17.9 KB
Line 
1// $Id: Configuration.cc 1144 2010-07-24 22:06:27Z peter $
2
3/*
4  Copyright (C) 2007, 2008, 2009, 2010 Jari Häkkinen, Peter Johansson
5
6  This file is part of svndigest, http://dev.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 3 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 svndigest. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#include <config.h>
23
24#include "Configuration.h"
25
26#include "Colors.h"
27#include "Functor.h"
28
29#include <algorithm>
30#include <cassert>
31#include <fstream>
32#include <map>
33#include <string>
34#include <sstream>
35#include <stdexcept>
36#include <utility>
37
38namespace theplu{
39namespace svndigest{
40
41  Configuration* Configuration::instance_=NULL;
42
43
44  Configuration::Configuration(void)
45  {
46  }
47
48
49  void Configuration::add_codon(std::string key, std::string start, 
50                                std::string end)
51  {
52    std::pair<std::string, std::string> p(start,end);
53    String2Codons::iterator iter = string2codons_.end();
54    for (String2Codons::iterator i=string2codons_.begin();
55         i!=string2codons_.end(); ++i)
56      if (i->first == key)
57        iter = i;
58   
59    if (iter==string2codons_.end())
60      string2codons_.push_back(std::make_pair(key, VectorPair(1,p)));
61    else
62      iter->second.push_back(p);
63  }
64
65
66  const std::map<std::string, std::string>&
67  Configuration::author_colors(void) const
68  {
69    return author_color_;
70  }
71
72  std::string Configuration::author_str_color(const std::string& author) const
73  {
74    std::string res;
75    std::map<std::string, std::string>::const_iterator iterator;
76    if ( (iterator=author_color_.find(author)) != author_color_.end())
77      res = iterator->second;
78    return res;
79  }
80
81
82  const std::vector<std::pair<std::string, std::string> >* 
83  Configuration::codon(std::string file_name) const 
84  {
85    if (const std::pair<std::string,std::string>* dict=dictionary(file_name))
86      file_name = translate(file_name, *dict);
87    for (String2Codons::const_iterator i(string2codons_.begin());
88         i!=string2codons_.end(); ++i) {
89      if (fnmatch(i->first.c_str(), file_name.c_str()))
90        return &i->second;
91    }
92    return NULL;
93  }
94
95
96  const std::map<std::string,Alias>& Configuration::copyright_alias(void) const
97  {
98    return copyright_alias_;
99  }
100
101
102  const std::pair<std::string,std::string>* 
103  Configuration::dictionary(std::string lhs) const
104  {
105    for (size_t i=0; i<dictionary_.size(); ++i)
106      if (fnmatch(lhs.c_str(), dictionary_[i].first.c_str()))
107        return &dictionary_[i];
108    return NULL;
109  }
110
111
112  bool Configuration::equal_false(std::string str) const
113  {
114    transform(str.begin(), str.end(), str.begin(), tolower);
115    return str=="false" || str=="no" || str=="off" || str=="0";
116  }
117
118
119  bool Configuration::equal_true(std::string str) const
120  {
121    transform(str.begin(), str.end(), str.begin(), tolower);
122    return str=="true" || str=="yes" || str=="on" || str=="1";
123  }
124
125
126  const std::string& Configuration::image_anchor_format(void) const
127  {
128    return image_anchor_format_;
129  }
130
131
132  const std::string& Configuration::image_format(void) const
133  {
134    return image_format_;
135  }
136
137
138  void Configuration::load(void)
139  {
140    set_default();
141    validate_dictionary();
142  }
143
144
145  void Configuration::load(std::istream& is)
146  {
147    assert(is.good());
148
149    bool parsing_found=false;
150    bool dictionary_found=false;
151    std::string line;
152    std::string section;
153    std::string tmp;
154    while (getline(is, line)) {
155      line = ltrim(line);
156      if (line.empty() || line[0]=='#')
157        continue;
158      std::stringstream ss(line);
159      if (line[0] == '[') {
160        getline(ss, tmp, '[');
161        getline(ss, section, ']');
162        continue;
163      }
164      std::string lhs;
165      getline(ss, lhs, '=');
166      lhs = trim(lhs);
167      std::string rhs;
168      getline(ss, rhs);
169      rhs = trim(rhs);
170      if (rhs.empty()){
171        throw Config_error(line, "expected format: <lhs> = <rhs>");
172      }
173      if (section == "copyright-alias"){
174        std::map<std::string,Alias>::iterator iter = 
175          copyright_alias_.lower_bound(lhs);
176        if (iter!=copyright_alias_.end() && iter->first==lhs){
177          std::stringstream mess;
178          mess << "in copright-alias section " << lhs << " defined twice.";
179          throw Config_error(line, mess.str());
180        }
181       
182        // insert alias
183        copyright_alias_.insert(iter,std::make_pair(lhs, Alias(rhs,copyright_alias_.size())));
184      }
185      else if (section == "trac"){
186        if (lhs=="trac-root")
187          trac_root_=rhs;
188        else {
189          std::stringstream mess;
190          mess << "in trac section" << lhs + " is invalid option.";
191          throw Config_error(line, mess.str());
192        }
193      }
194      else if (section == "output") {
195        if (lhs=="blame-information") {
196          if (equal_false(rhs))
197            output_blame_information_ = false;
198          else if (equal_true(rhs))
199            output_blame_information_ = true;
200          else {
201            throw Config_error(line, "");
202          }
203        }
204        else if (lhs=="file") {
205          if (equal_false(rhs))
206            output_file_ = false;
207          else if (equal_true(rhs))
208            output_file_ = true;
209          else {
210            throw Config_error(line, "");
211          }
212        }
213      }
214      else if (section == "copyright") {
215        if (lhs=="missing-copyright-warning") {
216          if (equal_false(rhs))
217            missing_copyright_warning_ = false;
218          else if (equal_true(rhs))
219            missing_copyright_warning_ = true;
220          else {
221            throw Config_error(line, "");
222          }
223        }
224      }
225      else if (section == "author-color") {
226        unsigned char r,g,b;
227        try {
228          str2rgb(rhs, r,g,b);
229        }
230        catch (std::runtime_error& e) {
231          throw Config_error(line, e.what());
232        }
233        author_color_[lhs] = rhs;
234      }     
235      else if (section == "parsing-codons") {
236        if (!parsing_found) {
237          parsing_found=true;
238          // clearing the default setting
239          string2codons_.clear();
240        }
241       
242        if (codon(lhs)) {
243          std::stringstream mess;
244          mess << "clashes with previous given file name pattern: ";
245          // find previous file-name-pattern
246          for (String2Codons::const_iterator i(string2codons_.begin());
247               i!=string2codons_.end(); ++i) {
248            if (fnmatch(lhs.c_str(), i->first.c_str())) {
249              mess << "`" << i->first << "'";
250              break;
251            }
252          }
253          throw Config_error(line, mess.str());
254        }
255        std::stringstream ss(rhs);
256        std::string start;
257        while (getline(ss, start, ':')) {
258          start = trim(start);
259          std::string end;
260          getline(ss, end, ';');
261          end = trim(end);
262          if (start.empty() && end.empty())
263            continue;
264          try {
265            if (start.empty() || start=="\"\"") {
266              throw std::runtime_error("start-code is empty");
267            }
268            else if (start.size()<3) {
269              std::stringstream mess;
270              mess << "start-code `" << start << "' is invalid";
271              throw std::runtime_error(mess.str());
272            }
273            start = trim(start, '"');
274            if (end.empty() || end=="\"\"") {
275              throw std::runtime_error("end-code is empty");
276            }
277            else if (end.size()<3) {
278              std::stringstream mess;
279              mess << "end-code `" << end << "' is invalid";
280              throw std::runtime_error(mess.str());
281            }
282            end = trim(end, '"');
283          }
284          catch (std::runtime_error& e){
285            throw Config_error(line, e.what());
286          }
287          replace(start, "\\n", "\n");
288          replace(end, "\\n", "\n");
289          add_codon(lhs, start, end);
290        }
291      } 
292      else if (section == "file-name-dictionary") {
293        if (!dictionary_found) {
294          dictionary_found=true;
295          // clearing the default setting
296          dictionary_.clear();
297        }
298       
299        if (const std::pair<std::string, std::string>* entry=dictionary(lhs)) {
300          std::stringstream mess;
301          mess << "clashes with previous given file name pattern: "
302               << "`" << entry->first << "'";
303          throw Config_error(line, mess.str());
304        }
305        lhs = trim(lhs);
306        rhs = trim(rhs);
307        if (!lhs.empty() && !rhs.empty()) 
308          dictionary_.push_back(std::make_pair(lhs, rhs));
309        else if (!lhs.empty() || !rhs.empty()) {
310          throw Config_error(line, "");
311        }
312      } 
313      else if (section == "image") {
314        if (lhs == "format") {
315          try {
316            image_format(rhs);
317          }
318          catch (std::runtime_error e) {
319            throw Config_error(line, 
320                               "unknown format: " + rhs + "\n" + e.what());
321          }
322        }
323        else if (lhs == "image_format") {
324          try {
325            image_anchor_format(rhs);
326          }
327          catch (std::runtime_error e) {
328            throw Config_error(line, 
329                               "unknown format: " + rhs + "\n" + e.what());
330          }
331        }
332      }
333    }
334    validate_dictionary();
335  }
336
337
338  void Configuration::image_anchor_format(const std::string& format)
339  {
340    if (format!="none" && format!="pdf" && format!="png" && format!="svg") {
341      std::ostringstream oss;
342      oss << "Valid arguments are:\n"
343          << "  - `none'\n"
344          << "  - `pdf'\n"
345          << "  - `png'\n"
346          << "  - `svg'";
347      throw std::runtime_error(oss.str());
348    }
349    image_anchor_format_ = format;
350  }
351
352
353  void Configuration::image_format(const std::string& format)
354  {
355    if (format!="none" && format!="png" && format!="svg") {
356      std::ostringstream oss;
357      oss << "Valid arguments are:\n"
358          << "  - `none'\n"
359          << "  - `png'\n"
360          << "  - `svg'";
361      throw std::runtime_error(oss.str());
362    }
363    image_format_ = format;
364  }
365
366
367  Configuration& Configuration::instance(void)
368  {
369    if (!instance_){
370      instance_ = new Configuration;
371      instance_->load();
372    }
373    return *instance_;
374  }
375
376
377  bool Configuration::missing_copyright_warning(void) const
378  {
379    return missing_copyright_warning_;
380  }
381
382
383  std::string
384  Configuration::translate(const std::string& str,
385                           const std::pair<std::string, std::string>& dic) const
386  {
387    std::string res;
388    std::vector<std::string> vec;
389    if (!regexp(dic.first, str, vec)) {
390      std::stringstream mess;
391      mess << "invalid config file: "
392           << "expression " << dic.first << " is invalid";
393      throw std::runtime_error(mess.str());       
394    }
395    for (std::string::const_iterator i(dic.second.begin()); 
396         i!=dic.second.end(); ++i) {
397      if (*i == '$') {
398        std::stringstream ss(std::string(i+1, dic.second.end()));
399        size_t n = 0;
400        ss >> n;
401        if (n>vec.size() || n==0){
402          std::stringstream mess;
403          mess << "invalid config file: "
404               << "expression " << dic.second << " is invalid";
405          if (n)
406            mess << "because " << n << " is a too large.";
407          throw std::runtime_error(mess.str());       
408        }
409        res += vec[n-1];
410        ++i;
411        if (n>9){
412          ++i;
413          if (n>99)
414            ++i;
415
416        }
417      }
418      else
419        res += *i;
420    }
421
422    return res;
423  }
424
425
426  std::string trans_end_code(std::string str)
427  {
428    if (str.size()>0 && str[str.size()-1]=='\n')
429      return str.substr(0, str.size()-1) + std::string("\\n");
430    return str;
431  }
432
433
434  std::string trans_beg_code(std::string str)
435  {
436    if (str.size()>0 && str[0]=='\n')
437      return std::string("\\n") + str.substr(1); 
438    return str;
439  }
440
441
442  std::string trim(std::string str, char c)
443  {
444    if (str.size()<2 || str[0]!=c || str[str.size()-1]!=c){
445      std::stringstream mess;
446      mess << "expected `" << str << "' to be surrounded by `" << c << "'";
447      throw std::runtime_error(mess.str());
448    }
449    return str.substr(1, str.size()-2);
450  }
451
452
453  void Configuration::set_default(void)
454  {
455    copyright_alias_.clear();
456    missing_copyright_warning_=false;
457    trac_root_ = "";
458
459    add_codon("*.ac", "#", "\n");
460    add_codon("*.ac", "dnl", "\n");
461    add_codon("*.am", "#", "\n");
462    add_codon("*.as", "#", "\n");
463    add_codon("*.as", "dnl", "\n");
464    add_codon("*.bat", "\nREM", "\n");
465    add_codon("*.bat", "\nrem", "\n");
466    add_codon("*.c", "//", "\n");
467    add_codon("*.c", "/*", "*/");
468    add_codon("*.cc", "//", "\n");
469    add_codon("*.cc", "/*", "*/");
470    add_codon("*.cpp", "//", "\n");
471    add_codon("*.cpp", "/*", "*/");
472    add_codon("*.css", "<!--", "-->");
473    add_codon("*.cxx", "//", "\n");
474    add_codon("*.cxx", "/*", "*/");
475    add_codon("*.h", "//", "\n");
476    add_codon("*.h", "/*", "*/");
477    add_codon("*.hh", "//", "\n");
478    add_codon("*.hh", "/*", "*/");
479    add_codon("*.hpp", "//", "\n");
480    add_codon("*.hpp", "/*", "*/");
481    add_codon("*.html", "<%--", "--%>");
482    add_codon("*.java", "//", "\n");
483    add_codon("*.java", "/*", "*/");
484    add_codon("*.jsp", "<!--", "-->");
485    add_codon("*.m", "%", "\n");
486    add_codon("*.m4", "#", "\n");
487    add_codon("*.m4", "dnl", "\n");
488    add_codon("*.pl", "#", "\n");
489    add_codon("*.pm", "#", "\n");
490    add_codon("*.R", "#", "\n");
491    add_codon("*.rss", "<!--", "-->");
492    add_codon("*.sgml", "<!--", "-->");
493    add_codon("*.sh", "#", "\n");
494    add_codon("*.shtml", "<!--", "-->");
495    add_codon("*.tex", "%", "\n");
496    add_codon("*.xhtml", "<!--", "-->");
497    add_codon("*.xml", "<!--", "-->");
498    add_codon("*.xsd", "<!--", "-->");
499    add_codon("*.xsl", "<!--", "-->");
500    add_codon("*config", "#", "\n");
501    add_codon("bootstrap", "#", "\n");
502    add_codon("Makefile", "#", "\n");
503
504    dictionary_ = VectorPair(1, std::make_pair("*.in", "$1"));
505    image_format_ = "png";
506    image_anchor_format_ = "png";
507    output_blame_information_ = true;
508    output_file_ = true;
509  }
510
511
512  bool Configuration::output_blame_information(void) const
513  {
514    return output_blame_information_;
515  }
516
517
518  bool Configuration::output_file(void) const
519  {
520    return output_file_;
521  }
522
523
524  const std::map<std::string, std::string>*
525  Configuration::svn_properties(const std::string&) const
526  {
527    return NULL;
528  }
529
530
531  std::string Configuration::trac_root(void) const
532  {
533    return trac_root_;
534  }
535
536
537  void Configuration::validate_dictionary(void) const
538  {
539    VectorPair::const_iterator end(dictionary_.end());
540    for (VectorPair::const_iterator iter(dictionary_.begin());iter!=end;++iter){
541      std::string word(iter->first);
542      replace(word, "*", "");
543      replace(word, "?", "");
544      // throws if dictionary is invalid
545      translate(word, *iter);
546    }
547  }
548
549
550  std::ostream& operator<<(std::ostream& os, const Configuration& conf)
551  {
552    os << "### This file configures various behaviors for svndigest\n"
553       << "### The commented-out below are intended to demonstrate how to use\n"
554       << "### this file.\n"
555       << "\n";
556
557    os << "### Section for setting output\n"
558       << "[output]\n"
559       << "# if true svndigest will output blame information for each file.\n"
560       << "blame-information = ";
561    if (conf.output_blame_information())
562      os << "yes\n";
563    else
564      os << "no\n";
565    os << "# if true report will have pages for files and not only "
566       << "directories.\n"
567       << "file = ";
568    if (conf.output_file())
569      os << "yes\n";
570    else
571      os << "no\n";
572
573    os << "\n### Section for setting behaviour of copyright update\n"
574       << "[copyright]\n"
575       << "# if true svndigest will warn if file has no copyright statement.\n"
576       << "missing-copyright-warning = ";
577    if (conf.missing_copyright_warning())
578      os << "yes\n";
579    else
580      os << "no\n";
581
582    os << "\n"
583       << "### Section for setting aliases used in copyright update\n"
584       << "[copyright-alias]\n"
585       << "# jdoe = John Doe\n";
586
587    typedef std::vector<std::pair<std::string, Alias> > vector;
588    vector vec;
589    std::back_insert_iterator<vector> back_insert_iterator(vec);
590    vec.reserve(conf.copyright_alias().size());
591    std::copy(conf.copyright_alias().begin(), conf.copyright_alias().end(),
592              back_insert_iterator);
593    // sort with respect to Alias.id
594    IdCompare id;
595    PairSecondCompare<const std::string, Alias, IdCompare> comp(id);
596    std::sort(vec.begin(),vec.end(), comp);
597             
598
599    for (vector::const_iterator i(vec.begin()); i!=vec.end(); ++i) {
600      os << i->first << " = " << i->second.name() << "\n";
601    }
602
603    os << "\n"
604       << "### Section for images\n"
605       << "[image]\n"
606       << "format = " << conf.image_format() << "\n";
607    os << "anchor_format = " << conf.image_anchor_format() << "\n";
608
609
610    os << "\n"
611       << "### Section for author color in plots and blame output.\n"
612       << "[author-color]\n"
613       << "# jdoe = 000000\n";
614    typedef std::map<std::string,std::string> str_map;
615    for (str_map::const_iterator i(conf.author_color_.begin());
616         i!=conf.author_color_.end(); ++i) {
617      os << i->first << " = " << i->second << "\n";
618    }
619
620    os << "\n"
621       << "### Section for setting trac environment\n"
622       << "[trac]\n"
623       << "# If trac-root is set, svndigest will create anchors to "
624       << "the Trac page.\n"
625       << "# trac-root = http://dev.thep.lu.se/svndigest/\n";
626    if (!conf.trac_root().empty())
627      os << "trac-root = " << conf.trac_root() << "\n";
628
629    if (!conf.dictionary_.empty()) {
630      os << "\n"
631         << "### Section for setting dictionary for file names.\n"
632         << "### Prior looking for file name pattern in section " 
633         << "[parsing-codons],\n"
634         << "### the file name may be translated according to the rules \n"
635         << "### in this section. In default setting there is, for example,\n"
636         << "### a rule to translate `<FILENAME>.in' to `<FILENAME>'.\n"
637         << "### The format of the entries is:\n"
638         << "###    file-name-pattern = new-name\n"
639         << "### Left hand side may contain wildcards (such as '*' and '?').\n"
640         << "### Right hand side may contain \"$i\", which will be replaced \n"
641         << "### with the ith wild card in lhs string.\n"
642         << "[file-name-dictionary]\n";
643      for (size_t i=0; i<conf.dictionary_.size(); ++i)
644        os << conf.dictionary_[i].first << " = " 
645           << conf.dictionary_[i].second << "\n"; 
646    }
647    if (!conf.string2codons_.empty()) {
648      os << "\n"
649         << "### Section for setting parsing modes\n"
650         << "### The format of the entries is:\n"
651         << "###   file-name-pattern = \"start-code\" : \"end-code\"\n"
652         << "### The file-name-pattern may contain wildcards (such as '*' "
653         << "and '?').\n"
654         << "### String \"\\n\" can be used for codons containing newline"
655         << "\n### character.\n"
656         << "[parsing-codons]\n";
657      for (size_t i=0; i<conf.string2codons_.size(); ++i) {
658        os << conf.string2codons_[i].first << " = "; 
659        for (size_t j=0; j<conf.string2codons_[i].second.size(); ++j) {
660          if (j)
661            os << "  ;  ";
662          os << "\"" << trans_beg_code(conf.string2codons_[i].second[j].first) 
663             << "\":\"" 
664             << trans_end_code(conf.string2codons_[i].second[j].second) 
665             << "\""; 
666        }
667        os << "\n";
668      }
669    }
670    return os;
671  }
672
673 
674  Config_error::Config_error(const std::string& line,const std::string& message)
675    : std::runtime_error(std::string("line: `") + line + 
676                         std::string("' is invalid.\n") + message)
677  {}
678
679}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.