1#
2#  PCMSolver, an API for the Polarizable Continuum Model
3#  Copyright (C) 2020 Roberto Di Remigio, Luca Frediani and contributors.
4#
5#  This file is part of PCMSolver.
6#
7#  PCMSolver is free software: you can redistribute it and/or modify
8#  it under the terms of the GNU Lesser General Public License as published by
9#  the Free Software Foundation, either version 3 of the License, or
10#  (at your option) any later version.
11#
12#  PCMSolver is distributed in the hope that it will be useful,
13#  but WITHOUT ANY WARRANTY; without even the implied warranty of
14#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#  GNU Lesser General Public License for more details.
16#
17#  You should have received a copy of the GNU Lesser General Public License
18#  along with PCMSolver.  If not, see <http://www.gnu.org/licenses/>.
19#
20#  For information on the complete list of contributors to the
21#  PCMSolver API, see: <http://pcmsolver.readthedocs.io/>
22#
23
24# Execute cloc.pl Perl script and wrap results in a nicer format.
25# cloc script by Al Danial, available at http://cloc.sourceforge.net/
26# licensed under the GNU General Public License v2
27# (c) Roberto Di Remigio  <roberto.d.remigio@uit.no>
28# licensed under the GNU Lesser General Public License
29
30import subprocess
31import os
32import time
33import sys
34import json
35import shlex
36
37
38def cloc_command(perl, cloc_script, args):
39    """Wrapper to the cloc.pl Perl script.
40
41    Keyword arguments:
42    perl -- perl executable
43    cloc_script -- the cloc.pl script
44    files -- list of files to parse
45    args -- additional list of command line arguments to cloc.pl
46    """
47    command = [perl, cloc_script] + args
48    try:
49        retcode = subprocess.call(command, shell=False)
50        if retcode < 0:
51            sys.stderr.write('{0} terminated by signal {1}'.format(command, -retcode))
52    except OSError as e:
53        sys.stderr.write("{0} execution failed: {1}".format(command, e))
54
55
56def bar_chart(perl, count_dir, scratch_dir, output_dir, is_total=False):
57    """Generates matplotlib script for lines of code bar chart.
58
59    Keyword arguments:
60    perl -- perl executable
61    count_dir -- directory where to count lines of code
62    scratch_dir -- where intermediate files (JSON, matplotlib scripts) are to be saved
63    output_dir -- where the bar charts are to be saved
64    is_total -- whether this is the global counting or not
65    """
66    if is_total:
67        template = os.path.join(os.path.dirname(__file__), 'total_bar_chart.txt')
68        with open(template, 'r') as tmp:
69            script = '\n' + tmp.read()
70        annotation = '\'PCMSolver\\nFiles: {0:d}\'.format(nr_files)'
71    else:
72        template = os.path.join(os.path.dirname(__file__), 'bar_chart.txt')
73        with open(template, 'r') as tmp:
74            script = '\n' + tmp.read()
75        annotation = '\'Folder: {0}\\nLanguage: {1}\\nFiles: {2:d}\'.format(tag, language, nr_files)'
76    tag = os.path.basename(count_dir)
77    cloc_data = count_lines_of_code(perl, count_dir, scratch_dir, tag, output_dir)
78    plot_script = os.path.join(scratch_dir, tag + '.py')
79    with open(plot_script, 'w+') as bar_plot:
80        bar_plot.write(header())
81        bar_plot.write(script.format(svg_dir=output_dir, tag=tag, data=cloc_data, annotation=annotation))
82    return plot_script
83
84
85def count_lines_of_code(perl, count_dir, scratch_dir, plot_name, svg_dir):
86    """Runs cloc.pl to get total count and returns data for matplotlib script.
87
88    Keyword arguments:
89    perl -- perl executable
90    count_dir -- directory where to run cloc.pl
91    scratch_dir -- where intermediate files (JSON and matplotlib script) are to be saved
92    plot_name -- name of the plot (e.g. cavity or solver)
93    svg_dir -- where the SVG will be saved
94    """
95    # yapf: disable
96    json_report = os.path.join(scratch_dir, 'cloc_temp.json')
97    cloc_options = [
98        count_dir
99      , '--json'
100      , '--report-file={}'.format(json_report)
101      , '--include-lang="C++","C","Fortran 77","Fortran 90","Fortran 95","Python"'
102      , '--exclude-dir="external,examples,doc,cmake"'
103      , '--fullpath', '--not-match-d="src/utils/getkw"'
104      , '--fullpath', '--not-match-d=\'/build\S[a-zA-Z0-9]+/\''
105      , '--force-lang="C++",hpp'
106      , '--force-lang="C",h'
107      , '--force-lang="Fortran 90",f'
108      , '--force-lang="Fortran 90",F'
109      , '--force-lang="Fortran 90",f95'
110      , '--force-lang="Fortran 90",F95'
111      , '--quiet'
112    ]
113    # yapf: enable
114    cloc_command(perl, os.path.join(os.path.dirname(__file__), 'cloc.pl'), shlex.split(' '.join(cloc_options)))
115    with open(json_report, 'r') as cloc_out:
116        data = json.load(cloc_out)
117    os.remove(json_report)
118    # Prepare data for bar chart
119    cloc_data = {
120        'nr_blanks': data['SUM']['blank'],
121        'nr_comments': data['SUM']['comment'],
122        'nr_files': data['SUM']['nFiles'],
123        'nr_cpp_code': data['C++']['code'] if 'C++' in data else 0,
124        'nr_c_code': data['C']['code'] if 'C' in data else 0,
125        'nr_fortran_code': data['Fortran 90']['code'] if 'Fortran 90' in data else 0,
126        'nr_python_code': data['Python']['code'] if 'Python' in data else 0
127    }
128    return cloc_data
129
130
131def header():
132    """Print header of matplotlib script generating bar chart.
133
134    Keyword arguments:
135    plot_script -- name of the plotting script
136    """
137
138    header = """#!/usr/bin/env python
139# Automatically generated on {now}
140# Data obtained from the cloc.pl Perl script.
141# cloc script by Al Danial, available at http://cloc.sourceforge.net/
142# licensed under the GNU General Public License v2
143# (c) Roberto Di Remigio  <roberto.d.remigio@uit.no>
144# licensed under the GNU Lesser General Public License
145    """.format(now=time.strftime('%c'))
146    return header
147