Skip to content

Commit 16f8760

Browse files
authored
feat: add pyproject.toml support for opts (#77)
1 parent d41f1f1 commit 16f8760

File tree

3 files changed

+115
-4
lines changed

3 files changed

+115
-4
lines changed

README.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,28 @@ Below is the help output::
167167
--docstring-length min_length max_length
168168
apply docformatter to docstrings of given length range
169169
--version show program's version number and exit
170+
--config CONFIG path to file containing docformatter options
170171

171172

172173
Possible exit codes:
173174

174175
- **1** - if any error encountered
175176
- **3** - if any file needs to be formatted (in ``--check`` mode)
176177

178+
docformatter options can also be stored in a configuration file. Currently only
179+
pyproject.toml is supported. Add section [tool.docformatter] with options listed using
180+
the same name as command line options. For example::
181+
182+
[tool.docformatter]
183+
recursive = true
184+
wrap-summaries = 82
185+
blank = true
186+
187+
Command line options take precedence. The configuration file can be passed with a full
188+
path, otherwise docformatter will look in the current directory. For example::
189+
190+
docformatter --config ~/.secret/path/to/pyproject.toml
191+
177192
Wrapping descriptions
178193
=====================
179194

docformatter.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -618,9 +618,50 @@ def _format_code_with_args(source, args):
618618
line_range=args.line_range)
619619

620620

621+
def find_config_file(args):
622+
"""Find the configuration file the user specified."""
623+
config_files = ["pyproject.toml"]
624+
flargs = {}
625+
626+
config_file = args[args.index('--config') + 1]
627+
628+
if os.path.isfile(config_file):
629+
argfile = os.path.basename(config_file)
630+
for f in config_files:
631+
if argfile == f:
632+
flargs = read_configuration_from_file(config_file)
633+
634+
return flargs
635+
636+
637+
def read_configuration_from_file(configfile):
638+
"""Read docformatter options from a configuration file."""
639+
flargs = {}
640+
fullpath, ext = os.path.splitext(configfile)
641+
filename = os.path.basename(fullpath)
642+
643+
if ext == ".toml":
644+
import tomli
645+
646+
if filename == "pyproject":
647+
with open(configfile, "rb") as f:
648+
config = tomli.load(f)
649+
result = config.get("tool", {}).get("docformatter", None)
650+
if result is not None:
651+
flargs = {k: v if isinstance(v, list) else str(v)
652+
for k, v in result.items()}
653+
654+
return flargs
655+
656+
621657
def _main(argv, standard_out, standard_error, standard_in):
622658
"""Run internal main entry point."""
623659
import argparse
660+
661+
flargs = {}
662+
if "--config" in argv:
663+
flargs = find_config_file(argv)
664+
624665
parser = argparse.ArgumentParser(description=__doc__, prog='docformatter')
625666
changes = parser.add_mutually_exclusive_group()
626667
changes.add_argument('-i', '--in-place', action='store_true',
@@ -630,44 +671,57 @@ def _main(argv, standard_out, standard_error, standard_in):
630671
help='only check and report incorrectly formatted '
631672
'files')
632673
parser.add_argument('-r', '--recursive', action='store_true',
674+
default=bool(flargs.get('recursive', False)),
633675
help='drill down directories recursively')
634676
parser.add_argument('-e', '--exclude', nargs='*',
635677
help='exclude directories and files by names')
636-
parser.add_argument('--wrap-summaries', default=79, type=int,
678+
parser.add_argument('--wrap-summaries',
679+
default=int(flargs.get('wrap-summaries', 79)),
680+
type=int,
637681
metavar='length',
638682
help='wrap long summary lines at this length; '
639683
'set to 0 to disable wrapping '
640684
'(default: %(default)s)')
641-
parser.add_argument('--wrap-descriptions', default=72, type=int,
685+
parser.add_argument('--wrap-descriptions',
686+
default=int(flargs.get('wrap-descriptions', 72)),
687+
type=int,
642688
metavar='length',
643689
help='wrap descriptions at this length; '
644690
'set to 0 to disable wrapping '
645691
'(default: %(default)s)')
646692
parser.add_argument('--blank', dest='post_description_blank',
647693
action='store_true',
694+
default=bool(flargs.get('blank', False)),
648695
help='add blank line after description')
649696
parser.add_argument('--pre-summary-newline',
650697
action='store_true',
698+
default=bool(flargs.get('pre-summary-newline', False)),
651699
help='add a newline before the summary of a '
652700
'multi-line docstring')
653701
parser.add_argument('--make-summary-multi-line',
654702
action='store_true',
703+
default=bool(flargs.get('make-summary-multi-line',
704+
False)),
655705
help='add a newline before and after the summary of a '
656706
'one-line docstring')
657707
parser.add_argument('--force-wrap', action='store_true',
708+
default=bool(flargs.get('force-wrap', False)),
658709
help='force descriptions to be wrapped even if it may '
659710
'result in a mess')
660711
parser.add_argument('--range', metavar='line', dest='line_range',
661-
default=None, type=int, nargs=2,
712+
default=flargs.get('range', None), type=int, nargs=2,
662713
help='apply docformatter to docstrings between these '
663714
'lines; line numbers are indexed at 1')
664715
parser.add_argument('--docstring-length', metavar='length',
665716
dest='length_range',
666-
default=None, type=int, nargs=2,
717+
default=flargs.get('docstring-length', None),
718+
type=int, nargs=2,
667719
help='apply docformatter to docstrings of given '
668720
'length range')
669721
parser.add_argument('--version', action='version',
670722
version='%(prog)s ' + __version__)
723+
parser.add_argument('--config',
724+
help='path to file containing docformatter options')
671725
parser.add_argument('files', nargs='+',
672726
help="files to format or '-' for standard in")
673727

test_docformatter.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,6 +1271,24 @@ def test_exclude_nothing(self):
12711271
self.assertEqual(test_exclude_nothing, ['/root/folder_one/one.py', '/root/folder_one/folder_three/three.py',
12721272
'/root/folder_two/two.py'])
12731273

1274+
def test_read_config_file(self):
1275+
f = open('pyproject.toml', 'w+')
1276+
try:
1277+
f.write('[tool.docformatter]\nrecursive = true\nwrap-summaries = 82\n')
1278+
f.close()
1279+
self.assertEqual(docformatter.find_config_file(['--config', 'pyproject.toml']),
1280+
{'recursive': 'True', 'wrap-summaries': '82'})
1281+
finally:
1282+
os.remove(f.name)
1283+
1284+
def test_missing_config_file(self):
1285+
self.assertEqual(docformatter.find_config_file(['--config', 'pyproject.toml']),
1286+
{})
1287+
1288+
def test_unsupported_config_file(self):
1289+
self.assertEqual(docformatter.find_config_file(['--config', 'tox.ini']),
1290+
{})
1291+
12741292
class TestSystem(unittest.TestCase):
12751293

12761294
def test_diff(self):
@@ -1506,6 +1524,30 @@ def test_check_mode_incorrect_docstring(self):
15061524
self.assertEqual(stderr.getvalue().strip(), filename,
15071525
msg='Changed file should be reported')
15081526

1527+
def test_cli_override_config_file(self):
1528+
f = open('pyproject.toml', 'w+')
1529+
try:
1530+
f.write('[tool.docformatter]\nrecursive = true\nwrap-summaries = 82\n')
1531+
f.close()
1532+
with temporary_file('''\
1533+
def foo():
1534+
"""
1535+
Hello world
1536+
"""
1537+
''') as filename:
1538+
process = run_docformatter(['--wrap-summaries=79',
1539+
filename])
1540+
self.assertEqual('''\
1541+
@@ -1,4 +1,2 @@
1542+
def foo():
1543+
- """
1544+
- Hello world
1545+
- """
1546+
+ """Hello world."""
1547+
''', '\n'.join(process.communicate()[0].decode().split('\n')[2:]))
1548+
finally:
1549+
os.remove(f.name)
1550+
15091551

15101552
def generate_random_docstring(max_indentation_length=32,
15111553
max_word_length=20,

0 commit comments

Comments
 (0)