source: trunk/lib/Configuration.cc @ 1315

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

merge patch release 0.9.1 into trunk

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