source: trunk/lib/File.cc @ 497

Last change on this file since 497 was 497, checked in by Peter Johansson, 14 years ago

preparing for more statistics by adding StatsType layer in structure on top of previous structure, i.e., NewStructure? = StatsType/OldStructure?

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