From 17305bd863d2722894a5c435ccfc175547e4b8d8 Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Fri, 10 Sep 2021 11:18:39 +0200 Subject: [PATCH] Interpret --skip arguments as relative paths The --skip arguments are interpreted as paths relative to the directories to check (the "file" arguments passed to codespell), in addition to being interpreted "as is". The benefit is that "codespell ." and "codespell $PWD" behave the same, you just pass relative paths to --skip. No more adding "./" to directory patterns. --- README.rst | 8 ++--- codespell_lib/_codespell.py | 40 ++++++++++++++++++------- codespell_lib/tests/test_basic.py | 49 +++++++++++++++++++++++++++---- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/README.rst b/README.rst index 9a3f7c74255..d25c0d4df9e 100644 --- a/README.rst +++ b/README.rst @@ -61,12 +61,12 @@ Comma-separated list of files to skip. It accepts globs as well. Examples: * to skip .eps & .txt files, invoke ``codespell --skip="*.eps,*.txt"`` -* to skip directories, invoke ``codespell --skip="./src/3rd-Party,./src/Test"`` +* to skip directories, invoke ``codespell --skip="src/3rd-Party,src/Test"`` Useful commands:: - codespell -d -q 3 --skip="*.po,*.ts,./src/3rdParty,./src/Test" + codespell -d -q 3 --skip="*.po,*.ts,src/3rdParty,src/Test" List all typos found except translation files and some directories. Display them without terminal colors and with a quiet level of 3. :: @@ -99,13 +99,13 @@ containing an entry named ``[codespell]``. Each command line argument can be specified in this file (without the preceding dashes), for example:: [codespell] - skip = *.po,*.ts,./src/3rdParty,./src/Test + skip = *.po,*.ts,src/3rdParty,src/Test count = quiet-level = 3 This is equivalent to running:: - codespell --quiet-level 3 --count --skip "*.po,*.ts,./src/3rdParty,./src/Test" + codespell --quiet-level 3 --count --skip "*.po,*.ts,src/3rdParty,src/Test" Any options specified in the command line will *override* options from the config file. diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py index 235a878d923..8f19c55a816 100644 --- a/codespell_lib/_codespell.py +++ b/codespell_lib/_codespell.py @@ -429,6 +429,10 @@ def parse_options(args): # Re-parse command line options to override config. options = parser.parse_args(list(args), namespace=options) + # Add a phony option than can be changed into a real option when we + # are ready to deprecate old patterns (./foo/bar instead of foo/bar) + options.deprecated = True + if not options.files: options.files.append('.') @@ -873,35 +877,51 @@ def main(*args): bad_count = 0 for filename in options.files: - # ignore hidden files + # ignore hidden file or directory if is_hidden(filename, options.check_hidden): continue if os.path.isdir(filename): for root, dirs, files in os.walk(filename): - if glob_match.match(root): # skip (absolute) directories + rel_root = os.path.relpath(root, filename) + # skip matching relative and "as is" directory paths + if ( + glob_match.match(rel_root) + or glob_match.match(root) and options.deprecated + ): del dirs[:] continue - if is_hidden(root, options.check_hidden): # dir itself hidden + + # ignore hidden directory + if is_hidden(root, options.check_hidden): continue + + # files for file_ in files: - # ignore hidden files in directories + # ignore hidden files if is_hidden(file_, options.check_hidden): continue - if glob_match.match(file_): # skip files + # skip base file name + if glob_match.match(file_): continue - fname = os.path.join(root, file_) - if glob_match.match(fname): # skip paths + abs_path = os.path.join(root, file_) + rel_path = os.path.join(rel_root, file_) + # skip matching relative and "as is" file paths + if ( + glob_match.match(rel_path) + or glob_match.match(abs_path) and options.deprecated + ): continue + bad_count += parse_file( - fname, colors, summary, misspellings, exclude_lines, + abs_path, colors, summary, misspellings, exclude_lines, file_opener, word_regex, ignore_word_regex, uri_regex, uri_ignore_words, context, options) - # skip (relative) directories + # skip base directory name dirs[:] = [dir_ for dir_ in dirs if not glob_match.match(dir_)] - elif not glob_match.match(filename): # skip files + elif not glob_match.match(filename): # skip file bad_count += parse_file( filename, colors, summary, misspellings, exclude_lines, file_opener, word_regex, ignore_word_regex, uri_regex, diff --git a/codespell_lib/tests/test_basic.py b/codespell_lib/tests/test_basic.py index bbf2ea47ddb..5674a9100a7 100644 --- a/codespell_lib/tests/test_basic.py +++ b/codespell_lib/tests/test_basic.py @@ -285,7 +285,18 @@ def test_encoding(tmpdir, capsys): def test_ignore(tmpdir, capsys): - """Test ignoring of files and directories.""" + """Test ignoring of files and directories. + + Test the following file hierarchy: + + tmpdir + ├── good.txt + ├── bad.txt + └── ignoredir +    └── subdir +     └── bad.txt + + """ d = str(tmpdir) goodtxt = op.join(d, 'good.txt') with open(goodtxt, 'w') as f: @@ -296,19 +307,47 @@ def test_ignore(tmpdir, capsys): f.write('abandonned') assert cs.main(d) == 1 assert cs.main('--skip=bad*', d) == 0 + assert cs.main('--skip=*bad*', d) == 0 assert cs.main('--skip=bad.txt', d) == 0 + assert cs.main('--skip=./bad.txt', d) == 0 # deprecated subdir = op.join(d, 'ignoredir') os.mkdir(subdir) + subdir = op.join(subdir, 'subdir') + os.mkdir(subdir) with open(op.join(subdir, 'bad.txt'), 'w') as f: f.write('abandonned') assert cs.main(d) == 2 + assert cs.main(goodtxt, badtxt) == 1 + assert cs.main('--skip=*.txt', d) == 0 + assert cs.main('--skip=*.txt', goodtxt, badtxt) == 0 assert cs.main('--skip=bad*', d) == 0 assert cs.main('--skip=*ignoredir*', d) == 1 + assert cs.main('--skip=*gnoredir*', d) == 1 assert cs.main('--skip=ignoredir', d) == 1 - assert cs.main('--skip=*ignoredir/bad*', d) == 1 - badjs = op.join(d, 'bad.js') - copyfile(badtxt, badjs) - assert cs.main('--skip=*.js', goodtxt, badtxt, badjs) == 1 + assert cs.main('--skip=ignoredir/', d) == 1 + assert cs.main('--skip=*ignoredir/subdir*', d) == 1 + assert cs.main('--skip=*gnoredir/subdi*', d) == 1 + assert cs.main('--skip=ignoredir/subdir', d) == 1 + assert cs.main('--skip=ignoredir/subdir/', d) == 1 + assert cs.main('--skip=*ignoredir/subdir/bad*', d) == 1 + assert cs.main('--skip=ignoredir/subdir/bad.txt', d) == 1 + assert cs.main('--skip=*subdir*', d) == 1 + assert cs.main('--skip=*ubd*', d) == 1 + assert cs.main('--skip=subdir', d) == 1 + assert cs.main('--skip=*subdir/bad*', d) == 1 + assert cs.main('--skip=subdir/bad*', d) == 2 + # test deprecated syntax from inside "tmpdir" + cwd = os.getcwd() + os.chdir(tmpdir) + assert cs.main('--skip=./ignoredir') == 1 + assert cs.main('--skip=./ignoredir/subdir/') == 1 + assert cs.main('--skip=./ignoredir/subdir/bad.txt') == 1 + assert cs.main('--skip=./ignoredir', '.') == 1 + os.chdir(cwd) + # test deprecated syntax from outside "tmpdir" + assert cs.main(f'--skip={d}/ignoredir', d) == 1 + assert cs.main(f'--skip={d}/ignoredir/subdir/', d) == 1 + assert cs.main(f'--skip={d}/ignoredir/subdir/bad.txt', d) == 1 def test_check_filename(tmpdir, capsys):