source: trunk/lib/File.cc @ 858

Last change on this file since 858 was 858, checked in by Jari Häkkinen, 12 years ago

Addresses #97. Merging non-gnuplot plot generation code. Gnuplot traces left and lot of work before publication quality plots are generated.

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