Skip to content

Commit

Permalink
feat(cli): support file configuration overrides -- @tonybaloney (#179)
Browse files Browse the repository at this point in the history
* initial prototype for file configuration

* only load the radon.cfg if it exists

* add some basic tests and fix some erroneous behaviours

* Put the configuration in a module property so that it is used for the default values. Changing configuration once module is imported will not take effect

* Update documentation.
  • Loading branch information
tonybaloney authored and rubik committed Sep 17, 2019
1 parent 4fa76b7 commit 92fa247
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 18 deletions.
100 changes: 93 additions & 7 deletions docs/commandline.rst
Expand Up @@ -14,6 +14,28 @@ Radon currently has four commands:
with Radon, you'll have to set the `RADONFILESENCODING` environment
variable to `UTF-8`.

Radon configuration files
-------------------------

When using radon regularly, you may want to specify default argument values in a configuration file.
For example, all of the radon commands have a ``--exclude`` and ``--ignore`` argument on the command-line.

Radon will look for the following files to determine default arguments:

* ``radon.cfg``
* ``setup.cfg``
* ``~/.radon.cfg``

Any radon configuration will be given in the INI-format, under the section ``[radon]``.

For example:

.. code-block:: ini
[radon]
exclude = test_*.py
cc_min = B
The :command:`cc` command
-------------------------

Expand Down Expand Up @@ -57,38 +79,52 @@ Options

.. option:: -x, --max

Set the maximum complexity rank to display.
Set the maximum complexity rank to display, defaults to ``F``.

Value can be set in a configuration file using the ``cc_max`` property.

.. option:: -n, --min

Set the minimum complexity rank to display.
Set the minimum complexity rank to display, defaults to ``A``.

Value can be set in a configuration file using the ``cc_min`` property.

.. option:: -a, --average

If given, at the end of the analysis show the average Cyclomatic
Complexity. This option is influenced by :option:`-x, --max` and
:option:`-n, --min` options.

Value can be set in a configuration file using the ``average`` property.

.. option:: --total-average

Like :option:`-a, --average`, but it is not influenced by `min` and `max`.
Every analyzed block is counted, no matter whether it is displayed or not.

Value can be set in a configuration file using the ``total_average`` property.

.. option:: -s, --show-complexity

If given, show the complexity score along with its rank.

Value can be set in a configuration file using the ``show_complexity`` property.

.. option:: -e, --exclude

Exclude files only when their path matches one of these glob patterns.
Usually needs quoting at the command line.

Value can be set in a configuration file using the ``exclude`` property.

.. option:: -i, --ignore

Ignore directories when their name matches one of these glob patterns: radon
won't even descend into them. By default, hidden directories (starting with
'.') are ignored.

Value can be set in a configuration file using the ``ignore`` property.

.. option:: -o, --order

The ordering function for the results. Can be one of:
Expand All @@ -97,6 +133,8 @@ Options
* `LINES`: order by line numbers;
* `ALPHA`: order by block names (alphabetically).

Value can be set in a configuration file using the ``order`` property.

.. option:: -j, --json

If given, the results will be converted into JSON. This is useful in case
Expand All @@ -112,6 +150,14 @@ Options
Does not count assert statements when computing complexity. This is because
Python can be run with an optimize flag which removes assert statements.

Value can be set in a configuration file using the ``no_assert`` property.

.. option:: -O, --output-file

Save output to the specified output file.

Value can be set in a configuration file using the ``output_file`` property.

Examples
++++++++

Expand Down Expand Up @@ -178,38 +224,56 @@ Options

.. option:: -x, --max

Set the maximum MI to display.
Set the maximum MI to display. Expects a letter between A-F. Defaults to ``C``.

Value can be set in a configuration file using the ``mi_max`` property.

.. option:: -n, --min

Set the minimum MI to display.
Set the minimum MI to display. Expects a letter between A-F. Defaults to ``A``.

Value can be set in a configuration file using the ``mi_min`` property.

.. option:: -e, --exclude

Exclude files only when their path matches one of these glob patterns.
Usually needs quoting at the command line.

Value can be set in a configuration file using the ``exclude`` property.

.. option:: -i, --ignore

Ignore directories when their name matches one of these glob patterns: radon
won't even descend into them. By default, hidden directories (starting with
'.') are ignored.

Value can be set in a configuration file using the ``ignore`` property.

.. option:: -m, --multi

If given, Radon will not count multiline strings as comments.
Most of the time this is safe since multiline strings are used as functions
docstrings, but one should be aware that their use is not limited to that
and sometimes it would be wrong to count them as comment lines.

Value can be set in a configuration file using the ``multi`` property.

.. option:: -s, --show

If given, the actual MI value is shown in results, alongside the rank.

Value can be set in a configuration file using the ``show_mi`` property.

.. option:: -j, --json

Format results in JSON.

.. option:: -O, --output-file

Save output to the specified output file.

Value can be set in a configuration file using the ``output_file`` property.


Examples
++++++++
Expand Down Expand Up @@ -267,12 +331,16 @@ Options
Exclude files only when their path matches one of these glob patterns.
Usually needs quoting at the command line.

Value can be set in a configuration file using the ``exclude`` property.

.. option:: -i, --ignore

Ignore directories when their name matches one of these glob patterns: radon
won't even descend into them. By default, hidden directories (starting with
'.') are ignored.

Value can be set in a configuration file using the ``ignore`` property.

.. option:: -s, --summary

If given, at the end of the analysis a summary of the gathered
Expand All @@ -282,6 +350,13 @@ Options

If given, the results will be converted into JSON.

.. option:: -O, --output-file

Save output to the specified output file.

Value can be set in a configuration file using the ``output_file`` property.


Examples
++++++++

Expand All @@ -301,7 +376,7 @@ matches ``path1/tests/*``.


The :command:`hal` command
-------------------------
--------------------------

.. program:: hal

Expand All @@ -318,24 +393,35 @@ Options

.. option:: -f, --functions

Compute the metrics on the *function* level, as opposed to the *file*
level.
Compute the metrics on the *function* level, as opposed to the *file* level.

Value can be set in a configuration file using the ``functions`` property.

.. option:: -e, --exclude

Exclude files when their path matches one of these glob patterns. Usually
needs quoting at the command line.

Value can be set in a configuration file using the ``exclude`` property.

.. option:: -i, --ignore

Refuse to descend into directories that match any of these glob patterns. By
default, hidden directories (starting with '.') are ignored.

Value can be set in a configuration file using the ``ignore`` property.

.. option:: -j, --json

Convert results into JSON. This is useful for exporting results to another
application.

.. option:: -O, --output-file

Save output to the specified output file.

Value can be set in a configuration file using the ``output_file`` property.


Examples
++++++++
Expand Down
80 changes: 70 additions & 10 deletions radon/cli/__init__.py
Expand Up @@ -4,21 +4,65 @@
import inspect
from contextlib import contextmanager
from mando import Program
import configparser
import os

import radon.complexity as cc_mod
from radon.cli.colors import BRIGHT, RED, RESET
from radon.cli.harvest import CCHarvester, RawHarvester, MIHarvester, HCHarvester


CONFIG_SECTION_NAME = 'radon'


class FileConfig(object):
'''
Yield default options by reading local configuration files.
'''
def __init__(self):
self.file_cfg = self.file_config()

def get_value(self, key, type, default):
if not self.file_cfg.has_option(CONFIG_SECTION_NAME, key):
return default
if type == int:
return self.file_cfg.getint(CONFIG_SECTION_NAME, key, fallback=default)
if type == bool:
return self.file_cfg.getboolean(CONFIG_SECTION_NAME, key, fallback=default)
else:
return self.file_cfg.get(CONFIG_SECTION_NAME, key, fallback=default)

@staticmethod
def file_config():
'''Return any file configuration discovered'''
config = configparser.ConfigParser()
if os.path.exists('radon.cfg'):
config.read_file(open('radon.cfg'))
config.read(['setup.cfg', os.path.expanduser('~/.radon.cfg')])
return config


_cfg = FileConfig()

program = Program(version=sys.modules['radon'].__version__)


@program.command
@program.arg('paths', nargs='+')
def cc(paths, min='A', max='F', show_complexity=False, average=False,
exclude=None, ignore=None, order='SCORE', json=False, no_assert=False,
show_closures=False, total_average=False, xml=False, codeclimate=False,
output_file=None, ):
def cc(paths, min=_cfg.get_value('cc_min', str, 'A'),
max=_cfg.get_value('cc_max', str, 'F'),
show_complexity=_cfg.get_value('show_complexity', bool, False),
average=_cfg.get_value('average', bool, False),
exclude=_cfg.get_value('exclude', str, None),
ignore=_cfg.get_value('ignore', str, None),
order=_cfg.get_value('order', str, 'SCORE'),
json=False,
no_assert=_cfg.get_value('no_assert', bool, False),
show_closures=_cfg.get_value('show_closures', bool, False),
total_average=_cfg.get_value('total_average', bool, False),
xml=False,
codeclimate=False,
output_file=_cfg.get_value('output_file', str, None), ):
'''Analyze the given Python modules and compute Cyclomatic
Complexity (CC).
Expand Down Expand Up @@ -71,8 +115,12 @@ def cc(paths, min='A', max='F', show_complexity=False, average=False,

@program.command
@program.arg('paths', nargs='+')
def raw(paths, exclude=None, ignore=None, summary=False, json=False,
output_file=None):
def raw(paths,
exclude=_cfg.get_value('exclude', str, None),
ignore=_cfg.get_value('ignore', str, None),
summary=False,
json=False,
output_file=_cfg.get_value('output_file', str, None), ):
'''Analyze the given Python modules and compute raw metrics.
:param paths: The paths where to find modules or packages to analyze. More
Expand All @@ -99,8 +147,16 @@ def raw(paths, exclude=None, ignore=None, summary=False, json=False,

@program.command
@program.arg('paths', nargs='+')
def mi(paths, min='A', max='C', multi=True, exclude=None, ignore=None,
show=False, json=False, sort=False, output_file=None):
def mi(paths,
min=_cfg.get_value('mi_min', str, 'A'),
max=_cfg.get_value('mi_max', str, 'C'),
multi=_cfg.get_value('multi', bool, True),
exclude=_cfg.get_value('exclude', str, None),
ignore=_cfg.get_value('ignore', str, None),
show=_cfg.get_value('show_mi', bool, False),
json=False,
sort=False,
output_file=_cfg.get_value('output_file', str, None), ):
'''Analyze the given Python modules and compute the Maintainability Index.
The maintainability index (MI) is a compound metric, with the primary aim
Expand Down Expand Up @@ -140,8 +196,12 @@ def mi(paths, min='A', max='C', multi=True, exclude=None, ignore=None,

@program.command
@program.arg("paths", nargs="+")
def hal(paths, exclude=None, ignore=None, json=False, functions=False,
output_file=None):
def hal(paths,
exclude=_cfg.get_value('exclude', str, None),
ignore=_cfg.get_value('ignore', str, None),
json=False,
functions=_cfg.get_value('functions', bool, False),
output_file=_cfg.get_value('output_file', str, None), ):
"""
Analyze the given Python modules and compute their Halstead metrics.
Expand Down
25 changes: 25 additions & 0 deletions radon/tests/conftest.py
@@ -0,0 +1,25 @@
import pytest
import textwrap
import os


class RadonConfig(object):
def __init__(self):
self._fname = os.path.join(os.path.dirname(__file__), 'radon.cfg')

def write(self, text):
_cfg = textwrap.dedent(text)
with open(self._fname, 'w') as cfg_f:
cfg_f.write("# Autogenerated from pytest \n[radon]\n")
cfg_f.write(_cfg)

def __del__(self):
with open(self._fname, 'w') as cfg_f:
cfg_f.write('# Session completed')


@pytest.fixture(scope="session")
def radon_config():
r = RadonConfig()
yield r
del r

0 comments on commit 92fa247

Please sign in to comment.