Skip to content

Commit

Permalink
feat: add --config cli argument (#1081)
Browse files Browse the repository at this point in the history
Co-authored-by: Nytelife26 <xtylerjrx@gmail.com>
  • Loading branch information
Reid Draper and Nytelife26 authored May 24, 2021
1 parent f68a511 commit b4bca54
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 45 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci-danger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ jobs:
steps:
- name: "[INIT] Checkout repository"
uses: actions/checkout@v2
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: "[INIT] Install Ruby ${{ matrix.ruby }}"
uses: ruby/setup-ruby@v1.71.0
with:
Expand Down
6 changes: 4 additions & 2 deletions proselint/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ def print_errors(filename, errors, output_json=False, compact=False):

@click.command(context_settings=CONTEXT_SETTINGS)
@click.version_option(__version__, '--version', '-v', message='%(version)s')
@click.option('--config', is_flag=False, type=click.Path(),
help="Path to configuration file.")
@click.option('--debug', '-d', is_flag=True, help="Give verbose output.")
@click.option('--clean', '-c', is_flag=True, help="Clear the cache.")
@click.option('--json', '-j', 'output_json', is_flag=True,
Expand All @@ -104,7 +106,7 @@ def print_errors(filename, errors, output_json=False, compact=False):
@click.option('--compact', is_flag=True, help="Shorten output.")
@click.argument('paths', nargs=-1, type=click.Path())
@close_cache_shelves_after
def proselint(paths=None, version=None, clean=None, debug=None,
def proselint(paths=None, config=None, version=None, clean=None, debug=None,
output_json=None, time=None, demo=None, compact=None):
"""Create the CLI for proselint, a linter for prose."""
if time:
Expand Down Expand Up @@ -138,7 +140,7 @@ def proselint(paths=None, version=None, clean=None, debug=None,
else:
f = click.open_file(
fp, 'r', encoding="utf-8", errors="replace")
errors = lint(f, debug=debug)
errors = lint(f, debug=debug, config_file_path=config)
num_errors += len(errors)
print_errors(fp, errors, output_json, compact=compact)
except Exception:
Expand Down
75 changes: 32 additions & 43 deletions proselint/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,10 @@
import json
import importlib

try:
import dbm
except ImportError:
import anydbm as dbm

PY3 = sys.version_info[0] == 3
if PY3:
string_types = str
else:
string_types = basestring,

import dbm
_cache_shelves = dict()

proselint_path = os.path.dirname(os.path.realpath(__file__))
home_dir = os.path.expanduser("~")


def close_cache_shelves():
Expand Down Expand Up @@ -56,13 +46,11 @@ def _get_xdg_path(variable_name, default_path):


def _get_xdg_config_home():
return _get_xdg_path('XDG_CONFIG_HOME',
os.path.join(os.path.expanduser('~'), '.config'))
return _get_xdg_path('XDG_CONFIG_HOME', os.path.join(home_dir, '.config'))


def _get_xdg_cache_home():
return _get_xdg_path('XDG_CACHE_HOME',
os.path.join(os.path.expanduser('~'), '.cache'))
return _get_xdg_path('XDG_CACHE_HOME', os.path.join(home_dir, '.cache'))


def _get_cache(cachepath):
Expand Down Expand Up @@ -100,10 +88,10 @@ def memoize(f):
"""Cache results of computations on disk."""
# Determine the location of the cache.
cache_dirname = os.path.join(_get_xdg_cache_home(), 'proselint')
legacy_cache_dirname = os.path.join(os.path.expanduser("~"), ".proselint")
legacy_cache_dirname = os.path.join(home_dir, ".proselint")

if not os.path.isdir(cache_dirname):
# Migrate the cache from the legacy path to XDG complaint location.
# Migrate the cache from the legacy path to XDG compliant location.
if os.path.isdir(legacy_cache_dirname):
os.rename(legacy_cache_dirname, cache_dirname)
# Create the cache if it does not already exist.
Expand All @@ -128,9 +116,9 @@ def wrapped(*args, **kwargs):
signature += item[1].encode("utf-8")

key = hashlib.sha256(signature).hexdigest()
cache = _get_cache(cachepath)

try:
cache = _get_cache(cachepath)
return cache[key]
except KeyError:
value = f(*args, **kwargs)
Expand Down Expand Up @@ -163,14 +151,14 @@ def get_checks(options):
return checks


def load_options():
def load_options(config_file_path=None, fallbacks=[]):
"""Read various proselintrc files, allowing user overrides."""
possible_defaults = (
'/etc/proselintrc',
os.path.join(proselint_path, '.proselintrc'),
)
user_options = {}
options = {}
has_overrides = False

for filename in possible_defaults:
try:
Expand All @@ -179,31 +167,28 @@ def load_options():
except IOError:
pass

try:
user_options = json.load(
open(os.path.join(_get_xdg_config_home(), 'proselint', 'config')))
# the user has explicitly passed in a config file
# either into the `lint' method directly, or via a
# Click command-line option
if config_file_path:
user_options = json.load(open(config_file_path))
has_overrides = True
except IOError:
pass
# try to find a config file in the default locations,
# respecting environment variables
else:
for f in fallbacks:
if os.path.exists(f):
user_options = json.load(open(f))
has_overrides = True
break

# Read user configuration from the legacy path.
if not has_overrides:
try:
user_options = json.load(
open(os.path.join(os.path.expanduser('~'), '.proselintrc')))
has_overrides = True
except IOError:
pass

if has_overrides:
if user_options:
if 'max_errors' in user_options:
options['max_errors'] = user_options['max_errors']
if 'checks' in user_options:
for (key, value) in user_options['checks'].items():
try:
options['checks'][key] = value
except KeyError:
pass
options['checks'][key] = value

return options

Expand Down Expand Up @@ -238,11 +223,15 @@ def line_and_column(text, position):
position_counter += len(line)


def lint(input_file, debug=False):
def lint(input_file, debug=False, config_file_path=None):
"""Run the linter on the input file."""
options = load_options()
options = load_options(
config_file_path,
[os.path.join(_get_xdg_config_home(), 'proselint', 'config'),
os.path.join(home_dir, '.proselintrc')]
)

if isinstance(input_file, string_types):
if isinstance(input_file, str):
text = input_file
else:
text = input_file.read()
Expand All @@ -261,7 +250,7 @@ def lint(input_file, debug=False):
(line, column) = line_and_column(text, start)
if not is_quoted(start, text):
errors += [(check, message, line, column, start, end,
end - start, "warning", replacements)]
end - start, "warning", replacements)]

if len(errors) > options["max_errors"]:
break
Expand Down
29 changes: 29 additions & 0 deletions tests/test_config_flag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Test user option overrides using --config and load_options"""
import subprocess
from proselint.tools import load_options


def test_load_options_function():
"""Test load_options by specifying a user options path"""
overrides = load_options("tests/test_config_flag_proselintrc")
assert load_options()["checks"]["uncomparables.misc"]
assert not overrides["checks"]["uncomparables.misc"]


def test_load_fallbacks():
"""Test load_options with a fallback path"""
fallbacks = load_options(None, ["tests/test_config_flag_proselintrc"])
assert not fallbacks["checks"]["uncomparables.misc"]
fallbacks = load_options(None, ["./.proselintrc"])
assert fallbacks["checks"]["uncomparables.misc"]


def test_config_flag():
"""Test the --config CLI argument"""
output = subprocess.run(["python", "-m", "proselint", "--demo"],
stdout=subprocess.PIPE, encoding='utf-8')
assert "uncomparables.misc" in output.stdout
output = subprocess.run(["python", "-m", "proselint", "--demo", "--config",
"tests/test_config_flag_proselintrc"],
stdout=subprocess.PIPE, encoding='utf-8')
assert "uncomparables.misc" not in output.stdout
85 changes: 85 additions & 0 deletions tests/test_config_flag_proselintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"max_errors": 1000,
"checks": {
"airlinese.misc" : true,
"annotations.misc" : true,
"archaism.misc" : true,
"cliches.hell" : true,
"cliches.misc" : true,
"consistency.spacing" : true,
"consistency.spelling" : true,
"corporate_speak.misc" : true,
"cursing.filth" : true,
"cursing.nfl" : false,
"cursing.nword" : true,
"dates_times.am_pm" : true,
"dates_times.dates" : true,
"hedging.misc" : true,
"hyperbole.misc" : true,
"jargon.misc" : true,
"lexical_illusions.misc" : true,
"lgbtq.offensive_terms" : true,
"lgbtq.terms" : true,
"links.broken" : false,
"malapropisms.misc" : true,
"misc.apologizing" : true,
"misc.back_formations" : true,
"misc.bureaucratese" : true,
"misc.but" : true,
"misc.capitalization" : true,
"misc.chatspeak" : true,
"misc.commercialese" : true,
"misc.composition" : true,
"misc.currency" : true,
"misc.debased" : true,
"misc.false_plurals" : true,
"misc.illogic" : true,
"misc.inferior_superior" : true,
"misc.institution_name" : true,
"misc.latin" : true,
"misc.many_a" : true,
"misc.metaconcepts" : true,
"misc.metadiscourse" : true,
"misc.narcissism" : true,
"misc.not_guilty" : true,
"misc.phrasal_adjectives" : true,
"misc.preferred_forms" : true,
"misc.pretension" : true,
"misc.professions" : true,
"misc.punctuation" : true,
"misc.scare_quotes" : true,
"misc.suddenly" : true,
"misc.tense_present" : true,
"misc.waxed" : true,
"misc.whence" : true,
"mixed_metaphors.misc" : true,
"mondegreens.misc" : true,
"needless_variants.misc" : true,
"nonwords.misc" : true,
"oxymorons.misc" : true,
"psychology.misc" : true,
"redundancy.misc" : true,
"redundancy.ras_syndrome" : true,
"skunked_terms.misc" : true,
"spelling.able_atable" : true,
"spelling.able_ible" : true,
"spelling.athletes" : true,
"spelling.em_im_en_in" : true,
"spelling.er_or" : true,
"spelling.in_un" : true,
"spelling.misc" : true,
"security.credit_card" : true,
"security.password" : true,
"sexism.misc" : true,
"terms.animal_adjectives" : true,
"terms.denizen_labels" : true,
"terms.eponymous_adjectives" : true,
"terms.venery" : true,
"typography.diacritical_marks" : true,
"typography.exclamation" : true,
"typography.symbols" : true,
"uncomparables.misc" : false,
"weasel_words.misc" : true,
"weasel_words.very" : true
}
}

0 comments on commit b4bca54

Please sign in to comment.