source: trunk/lib/Configuration.cc @ 1152

Last change on this file since 1152 was 1152, checked in by Peter Johansson, 11 years ago

allow config file to override svn property (fixes #326). Adding two new files (split.h and split.cc) from yat.

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