source: branches/0.9-stable/lib/Configuration.cc @ 1368

Last change on this file since 1368 was 1368, checked in by Peter Johansson, 10 years ago

updating copyright years

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