Removed the included 'radon' library and have it as an external dependency installed during the plug-in installation (for eric > 21.5). release-4.0.0

Thu, 13 May 2021 18:10:26 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 13 May 2021 18:10:26 +0200
changeset 80
a4f2000c6687
parent 79
66820b6b2a8a
child 81
c6c14f8b2e1a

Removed the included 'radon' library and have it as an external dependency installed during the plug-in installation (for eric > 21.5).

ChangeLog file | annotate | diff | comparison | revisions
PKGLIST file | annotate | diff | comparison | revisions
PluginMetricsRadon.epj file | annotate | diff | comparison | revisions
PluginMetricsRadon.py file | annotate | diff | comparison | revisions
PluginMetricsRadon.zip file | annotate | diff | comparison | revisions
RadonMetrics/Documentation/source/Plugin_Metrics_Radon.PluginMetricsRadon.html file | annotate | diff | comparison | revisions
RadonMetrics/radon/LICENSE file | annotate | diff | comparison | revisions
RadonMetrics/radon/__init__.py file | annotate | diff | comparison | revisions
RadonMetrics/radon/complexity.py file | annotate | diff | comparison | revisions
RadonMetrics/radon/metrics.py file | annotate | diff | comparison | revisions
RadonMetrics/radon/raw.py file | annotate | diff | comparison | revisions
RadonMetrics/radon/visitors.py file | annotate | diff | comparison | revisions
--- a/ChangeLog	Sun Apr 25 16:43:43 2021 +0200
+++ b/ChangeLog	Thu May 13 18:10:26 2021 +0200
@@ -1,5 +1,9 @@
 ChangeLog
 ---------
+Version 4.0.0:
+- removed the included 'radon' library and have it as an external
+  dependency installed during the plug-in installation (for eric > 21.5)
+
 Version 3.1.0:
 - upgraded embedded Radon library to version 4.5.0
 - implemented some code simplifications
--- a/PKGLIST	Sun Apr 25 16:43:43 2021 +0200
+++ b/PKGLIST	Thu May 13 18:10:26 2021 +0200
@@ -14,9 +14,3 @@
 RadonMetrics/i18n/radon_en.qm
 RadonMetrics/i18n/radon_es.qm
 RadonMetrics/i18n/radon_ru.qm
-RadonMetrics/radon/LICENSE
-RadonMetrics/radon/__init__.py
-RadonMetrics/radon/complexity.py
-RadonMetrics/radon/metrics.py
-RadonMetrics/radon/raw.py
-RadonMetrics/radon/visitors.py
--- a/PluginMetricsRadon.epj	Sun Apr 25 16:43:43 2021 +0200
+++ b/PluginMetricsRadon.epj	Thu May 13 18:10:26 2021 +0200
@@ -61,7 +61,7 @@
         "CopyrightMinFileSize": 0,
         "DocstringType": "eric",
         "EnabledCheckerCategories": "C, D, E, M, N, S, Y, W",
-        "ExcludeFiles": "*/Ui_*.py, */*_rc.py,*/radon/*",
+        "ExcludeFiles": "*/Ui_*.py, */*_rc.py",
         "ExcludeMessages": "C101,E265,E266,E305,E402,M201,M301,M302,M303,M304,M305,M306,M307,M308,M311,M312,M313,M314,M315,M321,M701,M702,M811,M834,N802,N803,N807,N808,N821,W293,W504,Y119,Y401,Y402",
         "FixCodes": "",
         "FixIssues": false,
@@ -175,7 +175,6 @@
       "PluginMetricsRadon.zip",
       "RadonMetrics/Documentation/LICENSE.GPL3",
       "RadonMetrics/Documentation/source",
-      "RadonMetrics/radon/LICENSE",
       "PluginMetricsRadon.epj"
     ],
     "OTHERTOOLSPARMS": {
@@ -205,11 +204,6 @@
       "RadonMetrics/MaintainabilityIndexDialog.py",
       "RadonMetrics/RawMetricsDialog.py",
       "RadonMetrics/__init__.py",
-      "RadonMetrics/radon/__init__.py",
-      "RadonMetrics/radon/complexity.py",
-      "RadonMetrics/radon/metrics.py",
-      "RadonMetrics/radon/raw.py",
-      "RadonMetrics/radon/visitors.py",
       "__init__.py"
     ],
     "SPELLEXCLUDES": "",
--- a/PluginMetricsRadon.py	Sun Apr 25 16:43:43 2021 +0200
+++ b/PluginMetricsRadon.py	Thu May 13 18:10:26 2021 +0200
@@ -27,7 +27,7 @@
 author = "Detlev Offenbach <detlev@die-offenbachs.de>"
 autoactivate = True
 deactivateable = True
-version = "3.1.0"
+version = "4.0.0"
 className = "RadonMetricsPlugin"
 packageName = "RadonMetrics"
 shortDescription = "Code metrics plugin using radon package"
@@ -984,5 +984,22 @@
         if self.__projectRawMetricsDialog:
             self.__projectRawMetricsDialog.clear()
 
+
+def installDependencies(pipInstall):
+    """
+    Function to install dependencies of this plug-in.
+    
+    @param pipInstall function to be called with a list of package names.
+    @type function
+    """
+    try:
+        from radon import __version__ as radon_version
+        import Globals
+        if Globals.versionToTuple(radon_version) < (4, 5, 0):
+            # force an upgrade
+            pipInstall(["radon>=4.5.0"])
+    except ImportError:
+        pipInstall(["radon>=4.5.0"])
+
 #
 # eflag: noqa = M801
Binary file PluginMetricsRadon.zip has changed
--- a/RadonMetrics/Documentation/source/Plugin_Metrics_Radon.PluginMetricsRadon.html	Sun Apr 25 16:43:43 2021 +0200
+++ b/RadonMetrics/Documentation/source/Plugin_Metrics_Radon.PluginMetricsRadon.html	Thu May 13 18:10:26 2021 +0200
@@ -42,7 +42,11 @@
 <h3>Functions</h3>
 
 <table>
-<tr><td>None</td></tr>
+
+<tr>
+<td><a href="#installDependencies">installDependencies</a></td>
+<td>Function to install dependencies of this plug-in.</td>
+</tr>
 </table>
 <hr />
 <hr />
@@ -723,4 +727,21 @@
 </dl>
 <div align="right"><a href="#top">Up</a></div>
 <hr />
+<hr />
+<a NAME="installDependencies" ID="installDependencies"></a>
+<h2>installDependencies</h2>
+<b>installDependencies</b>(<i>pipInstall</i>)
+
+<p>
+    Function to install dependencies of this plug-in.
+</p>
+<dl>
+
+<dt><i>pipInstall</i> (function)</dt>
+<dd>
+function to be called with a list of package names.
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
 </body></html>
\ No newline at end of file
--- a/RadonMetrics/radon/LICENSE	Sun Apr 25 16:43:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-Copyright (c) 2012-2017 Michele Lacchia
-
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation
-files (the "Software"), to deal in the Software without
-restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following
-conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
--- a/RadonMetrics/radon/__init__.py	Sun Apr 25 16:43:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-'''This module contains the main() function, which is the entry point for the
-command line interface.'''
-
-__version__ = '4.5.0'
-
-
-def main():
-    '''The entry point for Setuptools.'''
-    import sys
-    from radon.cli import program, log_error
-
-    if not sys.argv[1:]:
-        sys.argv.append('-h')
-    try:
-        program()
-    except Exception as e:
-        log_error(e)
-
-
-if __name__ == '__main__':
-    main()
--- a/RadonMetrics/radon/complexity.py	Sun Apr 25 16:43:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-'''This module contains all high-level helpers function that allow to work with
-Cyclomatic Complexity
-'''
-
-import math
-
-from radon.visitors import GET_COMPLEXITY, ComplexityVisitor, code2ast
-
-# sorted_block ordering functions
-SCORE = lambda block: -GET_COMPLEXITY(block)
-LINES = lambda block: block.lineno
-ALPHA = lambda block: block.name
-
-
-def cc_rank(cc):
-    r'''Rank the complexity score from A to F, where A stands for the simplest
-    and best score and F the most complex and worst one:
-
-    ============= =====================================================
-        1 - 5        A (low risk - simple block)
-        6 - 10       B (low risk - well structured and stable block)
-        11 - 20      C (moderate risk - slightly complex block)
-        21 - 30      D (more than moderate risk - more complex block)
-        31 - 40      E (high risk - complex block, alarming)
-        41+          F (very high risk - error-prone, unstable block)
-    ============= =====================================================
-
-    Here *block* is used in place of function, method or class.
-
-    The formula used to convert the score into an index is the following:
-
-    .. math::
-
-        \text{rank} = \left \lceil \dfrac{\text{score}}{10} \right \rceil
-        - H(5 - \text{score})
-
-    where ``H(s)`` stands for the Heaviside Step Function.
-    The rank is then associated to a letter (0 = A, 5 = F).
-    '''
-    if cc < 0:
-        raise ValueError('Complexity must be a non-negative value')
-    return chr(
-        min(int(math.ceil(cc / 10.0) or 1) - (1, 0)[5 - cc < 0], 5) + 65
-    )
-
-
-def average_complexity(blocks):
-    '''Compute the average Cyclomatic complexity from the given blocks.
-    Blocks must be either :class:`~radon.visitors.Function` or
-    :class:`~radon.visitors.Class`. If the block list is empty, then 0 is
-    returned.
-    '''
-    size = len(blocks)
-    if size == 0:
-        return 0
-    return sum((GET_COMPLEXITY(block) for block in blocks), 0.0) / len(blocks)
-
-
-def sorted_results(blocks, order=SCORE):
-    '''Given a ComplexityVisitor instance, returns a list of sorted blocks
-    with respect to complexity. A block is a either
-    :class:`~radon.visitors.Function` object or a
-    :class:`~radon.visitors.Class` object.
-    The blocks are sorted in descending order from the block with the highest
-    complexity.
-
-    The optional `order` parameter indicates how to sort the blocks. It can be:
-
-        * `LINES`: sort by line numbering;
-        * `ALPHA`: sort by name (from A to Z);
-        * `SCORE`: sorty by score (descending).
-
-    Default is `SCORE`.
-    '''
-    return sorted(blocks, key=order)
-
-
-def add_inner_blocks(blocks):
-    '''Process a list of blocks by adding all closures and inner classes as
-    top-level blocks.
-    '''
-    new_blocks = []
-    all_blocks = blocks[:]
-    while all_blocks:
-        block = all_blocks.pop()
-        new_blocks.append(block)
-        for inner_block in ('closures', 'inner_classes'):
-            for i_block in getattr(block, inner_block, ()):
-                named = i_block._replace(name=block.name + '.' + i_block.name)
-                all_blocks.append(named)
-                for meth in getattr(named, 'methods', ()):
-                    m_named = meth._replace(classname=named.name)
-                    all_blocks.append(m_named)
-    return new_blocks
-
-
-def cc_visit(code, **kwargs):
-    '''Visit the given code with :class:`~radon.visitors.ComplexityVisitor`.
-    All the keyword arguments are directly passed to the visitor.
-    '''
-    return cc_visit_ast(code2ast(code), **kwargs)
-
-
-def cc_visit_ast(ast_node, **kwargs):
-    '''Visit the AST node with :class:`~radon.visitors.ComplexityVisitor`. All
-    the keyword arguments are directly passed to the visitor.
-    '''
-    return ComplexityVisitor.from_ast(ast_node, **kwargs).blocks
--- a/RadonMetrics/radon/metrics.py	Sun Apr 25 16:43:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,157 +0,0 @@
-'''Module holding functions related to miscellaneous metrics, such as Halstead
-metrics or the Maintainability Index.
-'''
-
-import ast
-import collections
-import math
-
-from radon.raw import analyze
-from radon.visitors import ComplexityVisitor, HalsteadVisitor
-
-# Halstead metrics
-HalsteadReport = collections.namedtuple(
-    'HalsteadReport',
-    'h1 h2 N1 N2 vocabulary length '
-    'calculated_length volume '
-    'difficulty effort time bugs',
-)
-
-# `total` is a HalsteadReport for the entire scanned file, while `functions` is
-# a list of `HalsteadReport`s for each function in the file.
-Halstead = collections.namedtuple("Halstead", "total functions")
-
-
-def h_visit(code):
-    '''Compile the code into an AST tree and then pass it to
-    :func:`~radon.metrics.h_visit_ast`.
-    '''
-    return h_visit_ast(ast.parse(code))
-
-
-def h_visit_ast(ast_node):
-    '''
-    Visit the AST node using the :class:`~radon.visitors.HalsteadVisitor`
-    visitor. The results are `HalsteadReport` namedtuples with the following
-    fields:
-
-        * h1: the number of distinct operators
-        * h2: the number of distinct operands
-        * N1: the total number of operators
-        * N2: the total number of operands
-        * h: the vocabulary, i.e. h1 + h2
-        * N: the length, i.e. N1 + N2
-        * calculated_length: h1 * log2(h1) + h2 * log2(h2)
-        * volume: V = N * log2(h)
-        * difficulty: D = h1 / 2 * N2 / h2
-        * effort: E = D * V
-        * time: T = E / 18 seconds
-        * bugs: B = V / 3000 - an estimate of the errors in the implementation
-
-    The actual return of this function is a namedtuple with the following
-    fields:
-
-        * total: a `HalsteadReport` namedtuple for the entire scanned file
-        * functions: a list of `HalsteadReport`s for each toplevel function
-
-    Nested functions are not tracked.
-    '''
-    visitor = HalsteadVisitor.from_ast(ast_node)
-    total = halstead_visitor_report(visitor)
-    functions = [
-        (v.context, halstead_visitor_report(v))
-        for v in visitor.function_visitors
-    ]
-
-    return Halstead(total, functions)
-
-
-def halstead_visitor_report(visitor):
-    """Return a HalsteadReport from a HalsteadVisitor instance."""
-    h1, h2 = visitor.distinct_operators, visitor.distinct_operands
-    N1, N2 = visitor.operators, visitor.operands
-    h = h1 + h2
-    N = N1 + N2
-    if h1 and h2:
-        length = h1 * math.log(h1, 2) + h2 * math.log(h2, 2)
-    else:
-        length = 0
-    volume = N * math.log(h, 2) if h != 0 else 0
-    difficulty = (h1 * N2) / float(2 * h2) if h2 != 0 else 0
-    effort = difficulty * volume
-    return HalsteadReport(
-        h1,
-        h2,
-        N1,
-        N2,
-        h,
-        N,
-        length,
-        volume,
-        difficulty,
-        effort,
-        effort / 18.0,
-        volume / 3000.0,
-    )
-
-
-def mi_compute(halstead_volume, complexity, sloc, comments):
-    '''Compute the Maintainability Index (MI) given the Halstead Volume, the
-    Cyclomatic Complexity, the SLOC number and the number of comment lines.
-    Usually it is not used directly but instead :func:`~radon.metrics.mi_visit`
-    is preferred.
-    '''
-    if any(metric <= 0 for metric in (halstead_volume, sloc)):
-        return 100.0
-    sloc_scale = math.log(sloc)
-    volume_scale = math.log(halstead_volume)
-    comments_scale = math.sqrt(2.46 * math.radians(comments))
-    # Non-normalized MI
-    nn_mi = (
-        171
-        - 5.2 * volume_scale
-        - 0.23 * complexity
-        - 16.2 * sloc_scale
-        + 50 * math.sin(comments_scale)
-    )
-    return min(max(0.0, nn_mi * 100 / 171.0), 100.0)
-
-
-def mi_parameters(code, count_multi=True):
-    '''Given a source code snippet, compute the necessary parameters to
-    compute the Maintainability Index metric. These include:
-
-        * the Halstead Volume
-        * the Cyclomatic Complexity
-        * the number of LLOC (Logical Lines of Code)
-        * the percent of lines of comment
-
-    :param multi: If True, then count multiline strings as comment lines as
-        well. This is not always safe because Python multiline strings are not
-        always docstrings.
-    '''
-    ast_node = ast.parse(code)
-    raw = analyze(code)
-    comments_lines = raw.comments + (raw.multi if count_multi else 0)
-    comments = comments_lines / float(raw.sloc) * 100 if raw.sloc != 0 else 0
-    return (
-        h_visit_ast(ast_node).total.volume,
-        ComplexityVisitor.from_ast(ast_node).total_complexity,
-        raw.lloc,
-        comments,
-    )
-
-
-def mi_visit(code, multi):
-    '''Visit the code and compute the Maintainability Index (MI) from it.'''
-    return mi_compute(*mi_parameters(code, multi))
-
-
-def mi_rank(score):
-    r'''Rank the score with a letter:
-
-        * A if :math:`\text{score} > 19`;
-        * B if :math:`9 < \text{score} \le 19`;
-        * C if :math:`\text{score} \le 9`.
-    '''
-    return chr(65 + (9 - score >= 0) + (19 - score >= 0))
--- a/RadonMetrics/radon/raw.py	Sun Apr 25 16:43:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,245 +0,0 @@
-'''This module contains functions related to raw metrics.
-
-The main function is :func:`~radon.raw.analyze`, and should be the only one
-that is used.
-'''
-
-import collections
-import operator
-import tokenize
-
-try:
-    import StringIO as io
-except ImportError:  # pragma: no cover
-    import io
-
-
-__all__ = [
-    'OP',
-    'COMMENT',
-    'TOKEN_NUMBER',
-    'NL',
-    'NEWLINE',
-    'EM',
-    'Module',
-    '_generate',
-    '_fewer_tokens',
-    '_find',
-    '_logical',
-    'analyze',
-]
-
-COMMENT = tokenize.COMMENT
-OP = tokenize.OP
-NL = tokenize.NL
-NEWLINE = tokenize.NEWLINE
-EM = tokenize.ENDMARKER
-
-# Helper for map()
-TOKEN_NUMBER = operator.itemgetter(0)
-
-# A module object. It contains the following data:
-#   loc = Lines of Code (total lines)
-#   lloc = Logical Lines of Code
-#   comments = Comments lines
-#   multi = Multi-line strings (assumed to be docstrings)
-#   blank = Blank lines (or whitespace-only lines)
-#   single_comments = Single-line comments or docstrings
-Module = collections.namedtuple(
-    'Module',
-    ['loc', 'lloc', 'sloc', 'comments', 'multi', 'blank', 'single_comments'],
-)
-
-
-def _generate(code):
-    '''Pass the code into `tokenize.generate_tokens` and convert the result
-    into a list.
-    '''
-    # tokenize.generate_tokens is an undocumented function accepting text
-    return list(tokenize.generate_tokens(io.StringIO(code).readline))
-
-
-def _fewer_tokens(tokens, remove):
-    '''Process the output of `tokenize.generate_tokens` removing
-    the tokens specified in `remove`.
-    '''
-    for values in tokens:
-        if values[0] in remove:
-            continue
-        yield values
-
-
-def _find(tokens, token, value):
-    '''Return the position of the last token with the same (token, value)
-    pair supplied. The position is the one of the rightmost term.
-    '''
-    for index, token_values in enumerate(reversed(tokens)):
-        if (token, value) == token_values[:2]:
-            return len(tokens) - index - 1
-    raise ValueError('(token, value) pair not found')
-
-
-def _split_tokens(tokens, token, value):
-    '''Split a list of tokens on the specified token pair (token, value),
-    where *token* is the token type (i.e. its code) and *value* its actual
-    value in the code.
-    '''
-    res = [[]]
-    for token_values in tokens:
-        if (token, value) == token_values[:2]:
-            res.append([])
-            continue
-        res[-1].append(token_values)
-    return res
-
-
-def _get_all_tokens(line, lines):
-    '''Starting from *line*, generate the necessary tokens which represent the
-    shortest tokenization possible. This is done by catching
-    :exc:`tokenize.TokenError` when a multi-line string or statement is
-    encountered.
-    :returns: tokens, lines
-    '''
-    buffer = line
-    used_lines = [line]
-    while True:
-        try:
-            tokens = _generate(buffer)
-        except tokenize.TokenError:
-            # A multi-line string or statement has been encountered:
-            # start adding lines and stop when tokenize stops complaining
-            pass
-        else:
-            if not any(t[0] == tokenize.ERRORTOKEN for t in tokens):
-                return tokens, used_lines
-
-        # Add another line
-        next_line = next(lines)
-        buffer = buffer + '\n' + next_line
-        used_lines.append(next_line)
-
-
-def _logical(tokens):
-    '''Find how many logical lines are there in the current line.
-
-    Normally 1 line of code is equivalent to 1 logical line of code,
-    but there are cases when this is not true. For example::
-
-        if cond: return 0
-
-    this line actually corresponds to 2 logical lines, since it can be
-    translated into::
-
-        if cond:
-            return 0
-
-    Examples::
-
-        if cond:  -> 1
-
-        if cond: return 0  -> 2
-
-        try: 1/0  -> 2
-
-        try:  -> 1
-
-        if cond:  # Only a comment  -> 1
-
-        if cond: return 0  # Only a comment  -> 2
-    '''
-
-    def aux(sub_tokens):
-        '''The actual function which does the job.'''
-        # Get the tokens and, in the meantime, remove comments
-        processed = list(_fewer_tokens(sub_tokens, [COMMENT, NL, NEWLINE]))
-        try:
-            # Verify whether a colon is present among the tokens and that
-            # it is the last token.
-            token_pos = _find(processed, OP, ':')
-            # We subtract 2 from the total because the last token is always
-            # ENDMARKER. There are two cases: if the colon is at the end, it
-            # means that there is only one logical line; if it isn't then there
-            # are two.
-            return 2 - (token_pos == len(processed) - 2)
-        except ValueError:
-            # The colon is not present
-            # If the line is only composed by comments, newlines and endmarker
-            # then it does not count as a logical line.
-            # Otherwise it count as 1.
-            if not list(_fewer_tokens(processed, [NL, NEWLINE, EM])):
-                return 0
-            return 1
-
-    return sum(aux(sub) for sub in _split_tokens(tokens, OP, ';'))
-
-
-def is_single_token(token_number, tokens):
-    '''Is this a single token matching token_number followed by ENDMARKER, NL
-    or NEWLINE tokens.
-    '''
-    return TOKEN_NUMBER(tokens[0]) == token_number and all(
-        TOKEN_NUMBER(t) in (EM, NL, NEWLINE) for t in tokens[1:]
-    )
-
-
-def analyze(source):
-    '''Analyze the source code and return a namedtuple with the following
-    fields:
-
-        * **loc**: The number of lines of code (total)
-        * **lloc**: The number of logical lines of code
-        * **sloc**: The number of source lines of code (not necessarily
-            corresponding to the LLOC)
-        * **comments**: The number of Python comment lines
-        * **multi**: The number of lines which represent multi-line strings
-        * **single_comments**: The number of lines which are just comments with
-            no code
-        * **blank**: The number of blank lines (or whitespace-only ones)
-
-    The equation :math:`sloc + blanks + multi + single_comments = loc` should
-    always hold.  Multiline strings are not counted as comments, since, to the
-    Python interpreter, they are not comments but strings.
-    '''
-    lloc = comments = single_comments = multi = blank = sloc = 0
-    lines = (l.strip() for l in source.splitlines())
-    lineno = 1
-    for line in lines:
-        try:
-            # Get a syntactically complete set of tokens that spans a set of
-            # lines
-            tokens, parsed_lines = _get_all_tokens(line, lines)
-        except StopIteration:
-            raise SyntaxError('SyntaxError at line: {0}'.format(lineno))
-
-        lineno += len(parsed_lines)
-
-        comments += sum(
-            1 for t in tokens if TOKEN_NUMBER(t) == tokenize.COMMENT
-        )
-
-        # Identify single line comments, conservatively
-        if is_single_token(tokenize.COMMENT, tokens):
-            single_comments += 1
-
-        # Identify docstrings, conservatively
-        elif is_single_token(tokenize.STRING, tokens):
-            _, _, (start_row, _), (end_row, _), _ = tokens[0]
-            if end_row == start_row:
-                # Consider single-line docstrings separately from other
-                # multiline docstrings
-                single_comments += 1
-            else:
-                multi += sum(1 for l in parsed_lines if l)  # Skip empty lines
-                blank += sum(1 for l in parsed_lines if not l)
-        else:  # Everything else is either code or blank lines
-            for parsed_line in parsed_lines:
-                if parsed_line:
-                    sloc += 1
-                else:
-                    blank += 1
-
-        # Process logical lines separately
-        lloc += _logical(tokens)
-
-    loc = sloc + blank + multi + single_comments
-    return Module(loc, lloc, sloc, comments, multi, blank, single_comments)
--- a/RadonMetrics/radon/visitors.py	Sun Apr 25 16:43:43 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,453 +0,0 @@
-'''This module contains the ComplexityVisitor class which is where all the
-analysis concerning Cyclomatic Complexity is done. There is also the class
-HalsteadVisitor, that counts Halstead metrics.'''
-
-import ast
-import collections
-import operator
-
-# Helper functions to use in combination with map()
-GET_COMPLEXITY = operator.attrgetter('complexity')
-GET_REAL_COMPLEXITY = operator.attrgetter('real_complexity')
-NAMES_GETTER = operator.attrgetter('name', 'asname')
-GET_ENDLINE = operator.attrgetter('endline')
-
-BaseFunc = collections.namedtuple(
-    'Function',
-    [
-        'name',
-        'lineno',
-        'col_offset',
-        'endline',
-        'is_method',
-        'classname',
-        'closures',
-        'complexity',
-    ],
-)
-BaseClass = collections.namedtuple(
-    'Class',
-    [
-        'name',
-        'lineno',
-        'col_offset',
-        'endline',
-        'methods',
-        'inner_classes',
-        'real_complexity',
-    ],
-)
-
-
-def code2ast(source):
-    '''Convert a string object into an AST object.
-
-    This function is retained for backwards compatibility, but it no longer
-    attemps any conversions. It's equivalent to a call to ``ast.parse``.
-    '''
-    return ast.parse(source)
-
-
-class Function(BaseFunc):
-    '''Object represeting a function block.'''
-
-    @property
-    def letter(self):
-        '''The letter representing the function. It is `M` if the function is
-        actually a method, `F` otherwise.
-        '''
-        return 'M' if self.is_method else 'F'
-
-    @property
-    def fullname(self):
-        '''The full name of the function. If it is a method, then the full name
-        is:
-                {class name}.{method name}
-        Otherwise it is just the function name.
-        '''
-        if self.classname is None:
-            return self.name
-        return '{0}.{1}'.format(self.classname, self.name)
-
-    def __str__(self):
-        '''String representation of a function block.'''
-        return '{0} {1}:{2}->{3} {4} - {5}'.format(
-            self.letter,
-            self.lineno,
-            self.col_offset,
-            self.endline,
-            self.fullname,
-            self.complexity,
-        )
-
-
-class Class(BaseClass):
-    '''Object representing a class block.'''
-
-    letter = 'C'
-
-    @property
-    def fullname(self):
-        '''The full name of the class. It is just its name. This attribute
-        exists for consistency (see :data:`Function.fullname`).
-        '''
-        return self.name
-
-    @property
-    def complexity(self):
-        '''The average complexity of the class. It corresponds to the average
-        complexity of its methods plus one.
-        '''
-        if not self.methods:
-            return self.real_complexity
-        methods = len(self.methods)
-        return int(self.real_complexity / float(methods)) + (methods > 1)
-
-    def __str__(self):
-        '''String representation of a class block.'''
-        return '{0} {1}:{2}->{3} {4} - {5}'.format(
-            self.letter,
-            self.lineno,
-            self.col_offset,
-            self.endline,
-            self.name,
-            self.complexity,
-        )
-
-
-class CodeVisitor(ast.NodeVisitor):
-    '''Base class for every NodeVisitors in `radon.visitors`. It implements a
-    couple utility class methods and a static method.
-    '''
-
-    @staticmethod
-    def get_name(obj):
-        '''Shorthand for ``obj.__class__.__name__``.'''
-        return obj.__class__.__name__
-
-    @classmethod
-    def from_code(cls, code, **kwargs):
-        '''Instanciate the class from source code (string object). The
-        `**kwargs` are directly passed to the `ast.NodeVisitor` constructor.
-        '''
-        return cls.from_ast(code2ast(code), **kwargs)
-
-    @classmethod
-    def from_ast(cls, ast_node, **kwargs):
-        '''Instantiate the class from an AST node. The `**kwargs` are
-        directly passed to the `ast.NodeVisitor` constructor.
-        '''
-        visitor = cls(**kwargs)
-        visitor.visit(ast_node)
-        return visitor
-
-
-class ComplexityVisitor(CodeVisitor):
-    '''A visitor that keeps track of the cyclomatic complexity of
-    the elements.
-
-    :param to_method: If True, every function is treated as a method. In this
-        case the *classname* parameter is used as class name.
-    :param classname: Name of parent class.
-    :param off: If True, the starting value for the complexity is set to 1,
-        otherwise to 0.
-    '''
-
-    def __init__(
-        self, to_method=False, classname=None, off=True, no_assert=False
-    ):
-        self.off = off
-        self.complexity = 1 if off else 0
-        self.functions = []
-        self.classes = []
-        self.to_method = to_method
-        self.classname = classname
-        self.no_assert = no_assert
-        self._max_line = float('-inf')
-
-    @property
-    def functions_complexity(self):
-        '''The total complexity from all functions (i.e. the total number of
-        decision points + 1).
-
-        This is *not* the sum of all the complexity from the functions. Rather,
-        it's the complexity of the code *inside* all the functions.
-        '''
-        return sum(map(GET_COMPLEXITY, self.functions)) - len(self.functions)
-
-    @property
-    def classes_complexity(self):
-        '''The total complexity from all classes (i.e. the total number of
-        decision points + 1).
-        '''
-        return sum(map(GET_REAL_COMPLEXITY, self.classes)) - len(self.classes)
-
-    @property
-    def total_complexity(self):
-        '''The total complexity. Computed adding up the visitor complexity, the
-        functions complexity, and the classes complexity.
-        '''
-        return (
-            self.complexity
-            + self.functions_complexity
-            + self.classes_complexity
-            + (not self.off)
-        )
-
-    @property
-    def blocks(self):
-        '''All the blocks visited. These include: all the functions, the
-        classes and their methods. The returned list is not sorted.
-        '''
-        blocks = []
-        blocks.extend(self.functions)
-        for cls in self.classes:
-            blocks.append(cls)
-            blocks.extend(cls.methods)
-        return blocks
-
-    @property
-    def max_line(self):
-        '''The maximum line number among the analyzed lines.'''
-        return self._max_line
-
-    @max_line.setter
-    def max_line(self, value):
-        '''The maximum line number among the analyzed lines.'''
-        if value > self._max_line:
-            self._max_line = value
-
-    def generic_visit(self, node):
-        '''Main entry point for the visitor.'''
-        # Get the name of the class
-        name = self.get_name(node)
-        # Check for a lineno attribute
-        if hasattr(node, 'lineno'):
-            self.max_line = node.lineno
-        # The Try/Except block is counted as the number of handlers
-        # plus the `else` block.
-        # In Python 3.3 the TryExcept and TryFinally nodes have been merged
-        # into a single node: Try
-        if name in ('Try', 'TryExcept'):
-            self.complexity += len(node.handlers) + len(node.orelse)
-        elif name == 'BoolOp':
-            self.complexity += len(node.values) - 1
-        # Ifs, with and assert statements count all as 1.
-        # Note: Lambda functions are not counted anymore, see #68
-        elif name in ('If', 'IfExp'):
-            self.complexity += 1
-        # The For and While blocks count as 1 plus the `else` block.
-        elif name in ('For', 'While', 'AsyncFor'):
-            self.complexity += bool(node.orelse) + 1
-        # List, set, dict comprehensions and generator exps count as 1 plus
-        # the `if` statement.
-        elif name == 'comprehension':
-            self.complexity += len(node.ifs) + 1
-
-        super(ComplexityVisitor, self).generic_visit(node)
-
-    def visit_Assert(self, node):
-        '''When visiting `assert` statements, the complexity is increased only
-        if the `no_assert` attribute is `False`.
-        '''
-        self.complexity += not self.no_assert
-
-    def visit_AsyncFunctionDef(self, node):
-        '''Async function definition is the same thing as the synchronous
-        one.
-        '''
-        self.visit_FunctionDef(node)
-
-    def visit_FunctionDef(self, node):
-        '''When visiting functions a new visitor is created to recursively
-        analyze the function's body.
-        '''
-        # The complexity of a function is computed taking into account
-        # the following factors: number of decorators, the complexity
-        # the function's body and the number of closures (which count
-        # double).
-        closures = []
-        body_complexity = 1
-        for child in node.body:
-            visitor = ComplexityVisitor(off=False, no_assert=self.no_assert)
-            visitor.visit(child)
-            closures.extend(visitor.functions)
-            # Add general complexity but not closures' complexity, see #68
-            body_complexity += visitor.complexity
-
-        func = Function(
-            node.name,
-            node.lineno,
-            node.col_offset,
-            max(node.lineno, visitor.max_line),
-            self.to_method,
-            self.classname,
-            closures,
-            body_complexity,
-        )
-        self.functions.append(func)
-
-    def visit_ClassDef(self, node):
-        '''When visiting classes a new visitor is created to recursively
-        analyze the class' body and methods.
-        '''
-        # The complexity of a class is computed taking into account
-        # the following factors: number of decorators and the complexity
-        # of the class' body (which is the sum of all the complexities).
-        methods = []
-        # According to Cyclomatic Complexity definition it has to start off
-        # from 1.
-        body_complexity = 1
-        classname = node.name
-        visitors_max_lines = [node.lineno]
-        inner_classes = []
-        for child in node.body:
-            visitor = ComplexityVisitor(
-                True, classname, off=False, no_assert=self.no_assert
-            )
-            visitor.visit(child)
-            methods.extend(visitor.functions)
-            body_complexity += (
-                visitor.complexity
-                + visitor.functions_complexity
-                + len(visitor.functions)
-            )
-            visitors_max_lines.append(visitor.max_line)
-            inner_classes.extend(visitor.classes)
-
-        cls = Class(
-            classname,
-            node.lineno,
-            node.col_offset,
-            max(visitors_max_lines + list(map(GET_ENDLINE, methods))),
-            methods,
-            inner_classes,
-            body_complexity,
-        )
-        self.classes.append(cls)
-
-
-class HalsteadVisitor(CodeVisitor):
-    '''Visitor that keeps track of operators and operands, in order to compute
-    Halstead metrics (see :func:`radon.metrics.h_visit`).
-    '''
-
-    # As of Python 3.8 Num/Str/Bytes/NameConstat
-    # are now in a common node Constant.
-    types = {
-        "Num": "n",
-        "Name": "id",
-        "Attribute": "attr",
-        "Constant": "value",
-    }
-
-    def __init__(self, context=None):
-        '''*context* is a string used to keep track the analysis' context.'''
-        self.operators_seen = set()
-        self.operands_seen = set()
-        self.operators = 0
-        self.operands = 0
-        self.context = context
-
-        # A new visitor is spawned for every scanned function.
-        self.function_visitors = []
-
-    @property
-    def distinct_operators(self):
-        '''The number of distinct operators.'''
-        return len(self.operators_seen)
-
-    @property
-    def distinct_operands(self):
-        '''The number of distinct operands.'''
-        return len(self.operands_seen)
-
-    def dispatch(meth):
-        '''This decorator does all the hard work needed for every node.
-
-        The decorated method must return a tuple of 4 elements:
-
-            * the number of operators
-            * the number of operands
-            * the operators seen (a sequence)
-            * the operands seen (a sequence)
-        '''
-
-        def aux(self, node):
-            '''Actual function that updates the stats.'''
-            results = meth(self, node)
-            self.operators += results[0]
-            self.operands += results[1]
-            self.operators_seen.update(results[2])
-            for operand in results[3]:
-                new_operand = getattr(
-                    operand, self.types.get(type(operand), ''), operand
-                )
-                name = self.get_name(operand)
-                new_operand = getattr(
-                    operand, self.types.get(name, ""), operand
-                )
-
-                self.operands_seen.add((self.context, new_operand))
-            # Now dispatch to children
-            super(HalsteadVisitor, self).generic_visit(node)
-
-        return aux
-
-    @dispatch
-    def visit_BinOp(self, node):
-        '''A binary operator.'''
-        return (1, 2, (self.get_name(node.op),), (node.left, node.right))
-
-    @dispatch
-    def visit_UnaryOp(self, node):
-        '''A unary operator.'''
-        return (1, 1, (self.get_name(node.op),), (node.operand,))
-
-    @dispatch
-    def visit_BoolOp(self, node):
-        '''A boolean operator.'''
-        return (1, len(node.values), (self.get_name(node.op),), node.values)
-
-    @dispatch
-    def visit_AugAssign(self, node):
-        '''An augmented assign (contains an operator).'''
-        return (1, 2, (self.get_name(node.op),), (node.target, node.value))
-
-    @dispatch
-    def visit_Compare(self, node):
-        '''A comparison.'''
-        return (
-            len(node.ops),
-            len(node.comparators) + 1,
-            map(self.get_name, node.ops),
-            node.comparators + [node.left],
-        )
-
-    def visit_FunctionDef(self, node):
-        '''When visiting functions, another visitor is created to recursively
-        analyze the function's body. We also track information on the function
-        itself.
-        '''
-        func_visitor = HalsteadVisitor(context=node.name)
-
-        for child in node.body:
-            visitor = HalsteadVisitor.from_ast(child, context=node.name)
-            self.operators += visitor.operators
-            self.operands += visitor.operands
-            self.operators_seen.update(visitor.operators_seen)
-            self.operands_seen.update(visitor.operands_seen)
-
-            func_visitor.operators += visitor.operators
-            func_visitor.operands += visitor.operands
-            func_visitor.operators_seen.update(visitor.operators_seen)
-            func_visitor.operands_seen.update(visitor.operands_seen)
-
-        # Save the visited function visitor for later reference.
-        self.function_visitors.append(func_visitor)
-
-    def visit_AsyncFunctionDef(self, node):
-        '''Async functions are similar to standard functions, so treat them as
-        such.
-        '''
-        self.visit_FunctionDef(node)

eric ide

mercurial