source: trunk/lib/Configuration.cc @ 1135

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

fixes #330

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