source: trunk/lib/File.cc @ 816

Last change on this file since 816 was 816, checked in by Peter Johansson, 12 years ago

set color of author str in blame output if set in configuration. refs #161

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 12.5 KB
Line 
1// $Id: File.cc 816 2009-08-08 18:34:23Z peter $
2
3/*
4  Copyright (C) 2005, 2006, 2007 Jari Häkkinen, Peter Johansson
5  Copyright (C) 2009 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 "File.h"
24
25#include "Alias.h"
26#include "Configuration.h"
27#include "Date.h"
28#include "GnuplotFE.h"
29#include "html_utility.h"
30#include "HtmlStream.h"
31#include "Stats.h"
32#include "SVNblame.h"
33#include "SVNlog.h"
34
35#include <algorithm>
36#include <cassert>
37#include <cstdio>
38#include <ctime>
39#include <fstream>
40#include <iostream>
41#include <map>
42#include <stdexcept>
43#include <string>
44#include <sys/stat.h>
45
46namespace theplu{
47namespace svndigest{
48
49
50  File::File(const unsigned int level, const std::string& path, 
51             const std::string& output) 
52    : Node(level,path,output) 
53  {
54    output_dir_=output;
55    if (!output_dir_.empty())
56      output_dir_+='/';
57  }
58
59
60  std::string File::blame_output_file_name(void) const
61  {
62    return "blame_output/" + local_path() + ".html";
63  }
64
65
66  std::map<int, std::set<Alias> >
67  File::copyright_map(std::map<std::string, Alias>& alias,
68                      const std::map<int, svn_revnum_t>& year2rev) const
69  {
70    using namespace std;
71    map<int, set<Alias> > year_authors;
72    const Stats& stats = stats_["add"];
73
74    // loop over all years
75    for (std::map<int, svn_revnum_t>::const_iterator rev_iter=year2rev.begin();
76         rev_iter!=year2rev.end(); ++rev_iter) {
77
78      svn_revnum_t last_rev_this_year = rev_iter->second;
79      svn_revnum_t last_rev_last_year = 0;
80      if (rev_iter != year2rev.begin()) {
81        last_rev_last_year = (--rev_iter)->second;
82        ++rev_iter;
83      }
84      // loop over authors
85      for (std::set<std::string>::const_iterator a_iter=stats.authors().begin();
86           a_iter!=stats.authors().end(); ++a_iter) {
87
88        // check if anything has been added since last year
89        if ( (stats(LineTypeParser::code, *a_iter, last_rev_this_year) >
90              stats(LineTypeParser::code, *a_iter, last_rev_last_year)) || 
91             (stats(LineTypeParser::comment, *a_iter, last_rev_this_year) >
92              stats(LineTypeParser::comment, *a_iter, last_rev_last_year)) ) {
93       
94       
95          // find username in map of aliases
96          std::map<string,Alias>::iterator name(alias.lower_bound(*a_iter));
97         
98          // if alias exist insert alias
99          if (name != alias.end() && name->first==*a_iter)
100            year_authors[rev_iter->first].insert(name->second);
101          else {
102            // else insert user name
103            Alias a(*a_iter,alias.size());
104            year_authors[rev_iter->first].insert(a);
105            std::cerr << "svndigest: warning: no copyright alias found for `" 
106                      << *a_iter << "'\n";
107            // insert alias to avoid multiple warnings.
108            alias.insert(name, std::make_pair(*a_iter, a));
109          }
110        }
111      }
112    }
113    return year_authors;
114  }
115
116
117  std::string
118  File::copyright_block(const std::map<int, std::set<Alias> >& year_authors,
119                        const std::string& prefix) const
120  {
121    using namespace std;
122    stringstream ss;
123    for (map<int, set<Alias> >::const_iterator i(year_authors.begin());
124         i!=year_authors.end();) {
125      ss << prefix << "Copyright (C) "
126          << 1900+i->first;
127      map<int, set<Alias> >::const_iterator j = i;
128      assert(i!=year_authors.end());
129      while (++j!=year_authors.end() && 
130             i->second == j->second){
131        ss << ", " << 1900+(j->first);
132      }
133      // printing authors
134      std::vector<Alias> vec_alias;
135      back_insert_iterator<std::vector<Alias> > ii(vec_alias);
136      std::copy(i->second.begin(), i->second.end(), ii);
137      // sort with respect to id
138      std::sort(vec_alias.begin(), vec_alias.end(), IdCompare());
139      for (std::vector<Alias>::iterator a=vec_alias.begin();
140           a!=vec_alias.end(); ++a){
141        if (a!=vec_alias.begin())
142          ss << ",";
143        ss << " " << a->name();
144      }
145      ss << "\n";
146      i = j;
147    }
148    return ss.str();
149  }
150
151  bool File::detect_copyright(std::string& block, size_t& start_at_line,
152                              size_t& end_at_line, std::string& prefix) const
153  {
154    using namespace std;
155    LineTypeParser parser(path());
156    std::ifstream is(path().c_str());
157    std::string line;
158    while (std::getline(is, line)) 
159      parser.parse(line);
160    if (!parser.copyright_found())
161      return false;
162    block = parser.block();
163    start_at_line = parser.start_line();
164    end_at_line = parser.end_line();
165    prefix = parser.prefix();
166    return true;
167  }
168
169
170  std::string File::href(void) const
171  { 
172    return name()+".html"; 
173  }
174
175
176  void File::log_core(SVNlog&) const
177  {
178  }
179
180
181  std::string File::node_type(void) const
182  {
183    return std::string("file");
184  }
185
186
187  std::string File::output_path(void) const
188  {
189    return output_dir()+name()+".html";
190  }
191
192
193  const StatsCollection& File::parse(bool verbose, bool ignore)
194  {
195    if (verbose)
196      std::cout << "Parsing " << path_ << std::endl; 
197    stats_.reset();
198    std::string cache_dir = directory_name(path()) + std::string(".svndigest/"); 
199    std::string cache_file = cache_dir + name()+std::string(".svndigest-cache");
200    if (!ignore && node_exist(cache_file)){
201      std::ifstream is(cache_file.c_str());
202      if (stats_.load_cache(is)) {
203        is.close();
204        return stats_;
205      }
206      is.close();
207    }
208    else 
209      stats_.parse(path_);
210    if (!node_exist(cache_dir))
211      mkdir(cache_dir);
212    std::string tmp_cache_file(cache_file+"~");
213    std::ofstream os(tmp_cache_file.c_str());
214    stats_.print(os);
215    os.close();
216    rename(tmp_cache_file.c_str(), cache_file.c_str());
217    return stats_;
218  }
219
220
221  void File::print_blame(std::ofstream& os) const
222  {
223    os << "<br /><h3>" << local_path() << "</h3>";
224    os << "<table class=\"blame\">\n";
225    os << "<thead>\n";
226    os << "<tr>\n";
227    os << "<th class=\"rev\">Rev</th>\n";
228    os << "<th class=\"date\">Date</th>\n";
229    os << "<th class=\"author\">Author</th>\n";
230    os << "<th class=\"line\">Line</th>\n";
231    os << "<th></th>\n";
232    os << "</tr>\n</thead>\n";
233    os << "<tbody>\n";
234    HtmlStream hs(os);
235    SVNblame blame(path_);
236    LineTypeParser parser(path_);
237    while (blame.valid()) {
238      parser.parse(blame.line());
239      blame.next_line();
240    }
241    blame.reset();
242
243    std::vector<LineTypeParser::line_type>::const_iterator
244      line_type(parser.type().begin());
245    int last=0;
246    int first=0;
247    bool using_dates=true;
248    if (GnuplotFE::instance()->dates().empty()){
249      using_dates=false;
250      last = stats_["classic"].revision();
251    }
252    else {
253      last = Date(GnuplotFE::instance()->dates().back()).seconds();
254      // earliest date corresponds either to revision 0 or revision 1
255      first = std::min(Date(GnuplotFE::instance()->dates()[0]).seconds(),
256                       Date(GnuplotFE::instance()->dates()[1]).seconds());
257    }
258    // color is calculated linearly on time, c = kt + m
259    // brightest color (for oldest rev in log) is set to 192.
260    double k = 192.0/(first-last);
261    double m = -last*k; 
262    while (blame.valid()) {
263      std::string color;
264      if (using_dates)
265        color = hex(static_cast<int>(k*Date(blame.date()).seconds()+m),2);
266      else
267        color = hex(static_cast<int>(k*blame.revision()+m),2);
268      os << "<tr>\n<td class=\"rev\">";
269      std::stringstream color_ss;
270      color_ss << "#" << color << color << color; 
271      os << "<font color=\"" << color_ss.str() << "\">"
272         << trac_revision(blame.revision(), color_ss.str())
273         << "</font></td>\n<td class=\"date\"><font color=\"#" << color
274         << color << color << "\">" ;
275      hs << Date(blame.date())("%d %b %y");
276      os << "</font></td>\n<td class=\"author\">";
277      std::string author_color = 
278        Configuration::instance().author_str_color(blame.author()); 
279      if (!author_color.empty())
280        os << "<font color=\"#" << author_color << "\">";
281      hs << blame.author();
282      os << "</td>\n<td class=\"";
283      assert(line_type!=parser.type().end());
284      if (*line_type==LineTypeParser::other)
285        os << "line-other";
286      else if (*line_type==LineTypeParser::comment || 
287               *line_type==LineTypeParser::copyright)       
288        os << "line-comment";
289      else if (*line_type==LineTypeParser::code)
290        os << "line-code";
291      else {
292        std::string msg="File::print_blame(): unexpected line type found";
293        throw std::runtime_error(msg);
294      }
295      os << "\">" << blame.line_no()+1
296         << "</td>\n<td>";
297      hs << blame.line();
298      os << "</td>\n</tr>\n";
299      blame.next_line();
300      ++line_type;
301    }
302    os << "</tbody>\n";
303    os << "</table>\n";
304  }
305
306
307  void File::print_copyright(std::map<std::string, Alias>& alias, 
308                             bool verbose,
309                             const std::map<int,svn_revnum_t>& y2rev) const 
310  {
311    if (ignore())
312      return;
313
314    std::string old_block;
315    size_t start_line=0;
316    size_t end_line=0;
317    std::string prefix;
318    if (!detect_copyright(old_block, start_line, end_line, prefix)){
319      if (Configuration::instance().missing_copyright_warning())
320        std::cerr << "svndigest: warning: no copyright statement found in `" 
321                  << path_ << "'\n";
322      return;
323    }
324    std::map<int, std::set<Alias> > map=copyright_map(alias, y2rev);
325    std::string new_block = copyright_block(map, prefix);
326    if (old_block==new_block)
327      return;
328    if (verbose)
329      std::cout << "Updating copyright in " << path_ << std::endl; 
330    update_copyright(new_block, start_line, end_line);
331  }
332
333
334  void File::print_core(const bool verbose) const 
335  {
336    std::ofstream os(blame_output_file_name().c_str());
337    assert(os.good());
338    print_html_start(os, "svndigest", level_+1);
339    print_blame(os);
340    print_footer(os);
341    os.close();
342  }
343
344
345  void File::print_core(const std::string& stats_type, 
346                        const std::string& user, const std::string& line_type,
347                        const SVNlog& log) const 
348  {
349    std::string lpath = local_path();
350    if (lpath.empty())
351      lpath = "index";
352    std::string outpath = stats_type+"/"+user+"/"+line_type+"/"+lpath;
353    std::string imagefile = stats_type+"/"+"images/"+line_type+"/"+
354      lpath+".png";
355    std::string html_name(outpath + ".html");
356    std::ofstream os(html_name.c_str());
357    print_header(os, name(), level_+3, user, line_type, lpath+".html",
358                 stats_type);
359    path_anchor(os);
360    os << "<p class=\"plot\">\n<img src='"; 
361    for (size_t i=0; i<level_; ++i)
362      os << "../";
363    os << "../../../";
364    if (user=="all")
365      os << stats_[stats_type].plot(imagefile,line_type);
366    else
367      os << imagefile;
368    os << "' alt='[plot]' />\n</p>";
369
370    print_author_summary(os, stats_[stats_type], line_type, log);
371    os << "\n";
372    os << "<h3>"
373       << anchor(blame_output_file_name(), 
374                 "Blame Information", level_+3) 
375       << "</h3>\n";
376
377    print_footer(os);
378    os.close(); 
379  }
380
381  void File::update_copyright(const std::string& new_block,
382                              size_t start_at_line, size_t end_at_line) const
383  {
384    // Code copied from Gnuplot -r70
385    char tmpname[]="/tmp/svndigestXXXXXX";
386    int fd=mkstemp(tmpname);  // mkstemp return a file descriptor
387    if (fd == -1)
388      throw std::runtime_error(std::string("Failed to get unique filename: ") +
389                               tmpname);
390    // Jari would like to do something like 'std::ofstream tmp(fd);'
391    // but has to settle for (which is stupid since the file is
392    // already open for writing.
393    std::ofstream tmp(tmpname);
394
395    using namespace std;
396    ifstream is(path().c_str());
397    assert(is.good());
398    string line;
399    // Copy lines before block
400    for (size_t i=1; i<start_at_line; ++i){
401      assert(is.good());
402      getline(is, line);
403      tmp << line << "\n";
404    }
405    // Printing copyright statement
406    tmp << new_block;
407    // Ignore old block lines
408    for (size_t i=start_at_line; i<end_at_line; ++i){
409      assert(is.good());
410      getline(is, line);
411    }
412    // Copy lines after block
413    while(is.good()) {
414      char ch=is.get();
415      if (is.good())
416        tmp.put(ch);
417    }
418
419    is.close();
420    tmp.close();
421    close(fd);
422    // get file permission
423    struct stat nodestat;
424    stat(path().c_str(),&nodestat);
425   
426    // finally copy temporary file to replace original file, and
427    // remove the temporary file
428    try {
429      copy_file(tmpname, path());
430    }
431    catch (std::runtime_error e) {
432      // catch exception, cleanup, and rethrow
433      std::cerr << "svndigest: File::print_copyright: Exception caught, "
434                << "removing temporary file " << tmpname << std::endl;
435      if (unlink(tmpname))
436        throw runtime_error(std::string("File::print_copyright: ") +
437                            "failed to unlink temporary file" + tmpname);
438      throw;
439    }
440    if (unlink(tmpname))
441      throw runtime_error(std::string("File::print_copyright: ") +
442                          "failed to unlink temporary file" + tmpname);
443
444    chmod(path().c_str(), nodestat.st_mode);
445  }
446
447
448}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.