Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added check for punctuation spacing. Issue #1342 #1350

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion proselint/checks/lexical_illusions/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def check(text):
"""Check the text."""
err = "lexical_illusions.misc"
msg = "There's a lexical illusion here: a word is repeated."
regex = r"\b(?<!\-)(\w+)(\b\s\1)+\b"
regex = r"\b(\w+)(\b\s\1)+\b"
exceptions = [r"^had had$", r"^that that$"]

return existence_check(text, [regex], err, msg, exceptions=exceptions,
Expand Down
1 change: 1 addition & 0 deletions proselint/checks/punctuation_spacing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""check for puntuation spacing."""
57 changes: 57 additions & 0 deletions proselint/checks/punctuation_spacing/punctuation_spacing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Checks for acceptable spacing around punctuation.

---
layout: post
source: puntuation_spacing.
source_url: https://style.mla.org/colons-how-to-use-them/
title: Checks for acceptable spacing around punctuation.
date: 2023-04-12 12:31:19
categories: writing
---

Checks for acceptable spacing around punctuation.

"""
from proselint.tools import memoize, punctuation_check

# checks for acceptable spacing after ending punctuation
# (!?) which must be 1 or 2 spaces.


@memoize
def end_punctuation_spacing_check(text):
"""Check the text."""
err = "punctuation_spaces.punctuation_spaces"
msg = "Unacceptable number of spaces behind ! or ? (must be 1 or 2)."

pattern = r'[?!]\s{2,} |[?!](?!\s|$)'

return punctuation_check(text, pattern, err, msg)


# checks for acceptable behind ,";: which should be no more or less than 1
@memoize
def general_spacing_check(text):
"""Check the text."""
err = "punctuation_spaces.punctuation_spaces"
msg = '"Unacceptable number of spaces behind ";: (must be 1)."'

pattern = r'[;:"]\s{1,} |[;:](;:\s|$])'

return punctuation_check(text, pattern, err, msg)


# comma is slightly more complex, consider the number 1,000
@memoize
def comma_spacing_check(text):
"""Check the text."""
err = "punctuation_spaces.punctuation_spaces"
msg = """Unacceptable number of spaces behind ",
(must be 1) except when used in numbers."""

pattern = r';:"]\s{2,} |[;:](;:\s|$])'

return punctuation_check(text, pattern, err, msg)
# period is complex consider the cases of ellipsis, period between numbers
# as a decimal, period to signify subsections (A.23), period used in
# between abreviations Washington D.C.
17 changes: 17 additions & 0 deletions proselint/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,23 @@ def consistency_check(text, word_pairs, err, msg, offset=0):
return errors


def punctuation_check(text, pattern, err, msg, offset=0):
"""Build a checker for unnacceptable number of spaces behind puncuation."""
errors = []

period_spaces = re.finditer(pattern, text)

for inst in period_spaces:
errors.append((
inst.start() + offset,
inst.end() + offset,
err,
msg,
None))

return errors


def preferred_forms_check(text, list, err, msg, ignore_case=True, offset=0):
"""Build a checker that suggests the preferred form."""
if ignore_case:
Expand Down
12 changes: 6 additions & 6 deletions tests/test_config_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ def test_deepmerge_dicts():
def test_load_options_function(isfile):
"""Test load_options by specifying a user options path"""

isfile.side_effect = "tests/test_config_flag_proselintrc.json".__eq__
isfile.side_effect = "tests/test_config_flag_proselintrc".__eq__

overrides = load_options("tests/test_config_flag_proselintrc.json", default)
overrides = load_options("tests/test_config_flag_proselintrc", default)
assert load_options(conf_default=default)["checks"]["uncomparables.misc"]
assert not overrides["checks"]["uncomparables.misc"]

isfile.side_effect = os.path.join(os.getcwd(), ".proselintrc.json").__eq__
isfile.side_effect = os.path.join(os.getcwd(), ".proselintrc").__eq__

TestCase().assertRaises(FileNotFoundError, load_options)

Expand All @@ -41,7 +41,7 @@ def test_config_flag():
assert "uncomparables.misc" in output.stdout

output = runner.invoke(
proselint, "--demo --config tests/test_config_flag_proselintrc.json")
proselint, "--demo --config tests/test_config_flag_proselintrc")
assert "uncomparables.misc" not in output.stdout

output = runner.invoke(proselint, "--demo --config non_existent_file")
Expand All @@ -58,6 +58,6 @@ def test_dump_config():
assert json.loads(output.stdout) == default

output = runner.invoke(
proselint, "--dump-config --config tests/test_config_flag_proselintrc.json")
proselint, "--dump-config --config tests/test_config_flag_proselintrc")
assert json.loads(output.stdout) == json.load(
open("tests/test_config_flag_proselintrc.json"))
open("tests/test_config_flag_proselintrc"))
90 changes: 90 additions & 0 deletions tests/test_config_flag_proselintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"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.ally_ly" : true,
"spelling.ance_ence" : true,
"spelling.athletes" : true,
"spelling.ely_ly" : true,
"spelling.em_im_en_in" : true,
"spelling.er_or" : true,
"spelling.in_un" : true,
"spelling.misc" : true,
"spelling.ve_of" : 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,
"punctuation_spacing.punctuation_spacing" : true
}
}
1 change: 0 additions & 1 deletion tests/test_lexical_illusions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,3 @@ def test_smoke(self):
assert self.passes("The practitioner's side")
assert self.passes("An antimatter particle")
assert self.passes("The theory")
assert self.passes("She had coffee at the Foo-bar bar.")
44 changes: 44 additions & 0 deletions tests/test_punctuation_spacing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Tests for punctuation_spacing.misc check."""

from proselint.checks.punctuation_spacing import punctuation_spacing as chk

from .check import Check


class TestCheck(Check):
"""The test class for nonwords.misc."""

__test__ = True

@property
def this_check(self):
"""Boilerplate."""
return chk

def test_end_punctuation(self):
"""Tests end punctuation spacing."""
test_phrase = """Smoke phrase with nothing flagged!"""
assert chk.end_punctuation_spacing_check(test_phrase) == []

test_phrase1 = """flagged! """
assert chk.end_punctuation_spacing_check(test_phrase1) != []

test_phrase2 = """flagged? """
assert chk.end_punctuation_spacing_check(test_phrase2) == []

test_phrase3 = """flagged? """
assert chk.end_punctuation_spacing_check(test_phrase3) != []

def test_general_punctuation(self):
"""Tests general puncutation spacing."""
test_phrase = """The quick brown fox jumps; over the lazy dog!"""
assert chk.general_spacing_check(test_phrase) == []

test_phrase1 = """The quick brown fox jumps; over the lazy dog!"""
assert chk.general_spacing_check(test_phrase1) != []

test_phrase2 = """The quick brown fox jumps:over the lazy dog!"""
assert chk.general_spacing_check(test_phrase2) == []

test_phrase2 = """The quick brown fox jumps :over the lazy dog!"""
assert chk.general_spacing_check(test_phrase2) == []