././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1628227 proselint-0.13.0/LICENSE.md0000644000000000000000000000276400000000000012116 0ustar00Copyright © 2014–2015, Jordan Suchow, Michael Pacer, and Lara A. Ross All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1628227 proselint-0.13.0/README.md0000644000000000000000000002642300000000000011767 0ustar00proselint logo ![Workflow status](https://github.com/amperser/proselint/actions/workflows/ci-lint-test.yml/badge.svg) [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) [![Code Climate](https://codeclimate.com/repos/5538989ee30ba0793100090f/badges/e10a2fe18a9256d69e2a/gpa.svg)](https://codeclimate.com/repos/5538989ee30ba0793100090f/feed) [![codecov](https://codecov.io/gh/amperser/proselint/branch/main/graph/badge.svg?token=8E0I9sRpot)](https://codecov.io/gh/amperser/proselint) [![License](https://img.shields.io/badge/License-BSD-blue.svg)](https://en.wikipedia.org/wiki/BSD_licenses) Writing is notoriously hard, even for the best writers, and it's not for lack of good advice — a tremendous amount of knowledge about the craft is strewn across usage guides, dictionaries, technical manuals, essays, pamphlets, websites, and the hearts and minds of great authors and editors. But poring over Strunk & White hardly makes one a better writer — it turns you into neither Strunk nor White. And nobody has the capacity to apply all the advice from *Garner’s Modern English Usage*, an 1100-page usage guide, to everything they write. In fact, the whole notion that one becomes a better writer by reading advice on writing rests on untenable assumptions about learning and memory. The traditional formats of knowledge about writing are thus essentially inert, waiting to be transformed. We devised a simple solution: `proselint`, a linter for English prose. A linter is a computer program that, akin to a spell checker, scans through a file and detects issues — like how a real lint roller helps you get unwanted lint off of your shirt. `proselint` places the world's greatest writers and editors by your side, where they whisper suggestions on how to improve your prose. You’ll be guided by advice inspired by Bryan Garner, David Foster Wallace, Chuck Palahniuk, Steve Pinker, Mary Norris, Mark Twain, Elmore Leonard, George Orwell, Matthew Butterick, William Strunk, Elwyn White, Philip Corbett, Ernest Gowers, and the editorial staff of the world’s finest literary magazines and newspapers, among others. Our goal is to aggregate knowledge about best practices in writing and to make that knowledge immediately accessible to all authors in the form of a linter for prose; all in a neat command-line utility that you can integrate into other tools, scripts, and workflows. ### Installation To get this up and running, install it using [pip]: ```bash pip install proselint ``` [pip]: https://packaging.python.org/installing/#use-pip-for-installing #### Fedora ```bash sudo dnf install proselint ``` #### Debian ```bash sudo apt install python3-proselint ``` #### Ubuntu ```bash sudo add-apt-repository universe sudo apt install python3-proselint ``` ### Plugins for other software `proselint` is available on: - A [demo editor](http://proselint.com/write) - [Sublime Text](https://github.com/amperser/proselint/tree/main/plugins/sublime/SublimeLinter-contrib-proselint) - [Atom Editor](https://github.com/smockle/linter-proselint) (thanks to [Clay Miller](https://github.com/smockle)). - [Emacs via Flycheck](http://www.flycheck.org/). - Vim via [ALE](https://github.com/w0rp/ale) or [Syntastic](https://github.com/vim-syntastic/syntastic) (thanks to @lcd047, @Carreau, and [Daniel M. Capella](https://github.com/polyzen)) - [Phabricator's `arc` CLI](https://github.com/google/arc-proselint) (thanks to [Jeff Verkoeyen](https://github.com/jverkoey)) - [Danger](https://github.com/dbgrandi/danger-prose) (thanks to [David Grandinetti](https://github.com/dbgrandi) and [Orta Therox](https://github.com/orta)) - [Visual Studio Code](https://github.com/ppeszko/vscode-proselint) (thanks to [Patryk Peszko](https://github.com/ppeszko)) - [coala](https://github.com/coala-analyzer/bear-docs/blob/master/docs/ProseLintBear.rst) (thanks to the [coala Development Group](https://github.com/coala-analyzer)) - [IntelliJ](https://github.com/kropp/intellij-proselint) (by [Victor Kropp](https://github.com/kropp)) - [pre-commit](https://pre-commit.com/) (by [Andy Airey](https://github.com/aairey)) - [Statick](https://github.com/sscpac/statick-md) ### Usage Suppose you have a document `text.md` with the following text: ``` John is very unique. ``` You can run `proselint` over the document using the command line: ```bash proselint text.md ``` This prints a list of suggestions to stdout, one per line. Each suggestion has the form: ```bash text.md::: ``` For example, ```bash text.md:0:10: wallace.uncomparables Comparison of an uncomparable: 'unique' cannot be compared. ``` The command-line utility can also print suggestions in JSON using the `--json` flag. In this case, the output is considerably richer: ```jsonc { // Type of check that output this suggestion. check: "wallace.uncomparables", // Message to describe the suggestion. message: "Comparison of an uncomparable: 'unique' cannot be compared.", // The person or organization giving the suggestion. source: "David Foster Wallace" // URL pointing to the source material. source_url: "http://www.telegraph.co.uk/a/9715551" // Line where the error starts. line: 0, // Column where the error starts. column: 10, // Index in the text where the error starts. start: 10, // Index in the text where the error ends. end: 21, // length from start -> end extent: 11, // How important is this? Can be "suggestion", "warning", or "error". severity: "warning", // Possible replacements. replacements: [ { value: "unique" } ] } ``` To run the linter as part of another Python program, you can use the `lint` function in `proselint.tools`: ```python import proselint suggestions = proselint.tools.lint("This sentence is very unique") ``` This will return a list of suggestions: ```python [('weasel_words.very', "Substitute 'damn' every time you're inclined to write 'very;' your editor will delete it and the writing will be just as it should be.", 0, 17, 17, 22, 5, 'warning', None), ('uncomparables.misc', "Comparison of an uncomparable: 'very unique.' is not comparable.", 0, 17, 17, 29, 12, 'warning', None)] ``` ### Checks You can disable any of the checks by modifying `$XDG_CONFIG_HOME/proselint/config`. If `$XDG_CONFIG_HOME` is not set or empty, `~/.config/proselint/config` will be used. Additionally, for compatibility reasons, the legacy configuration `~/.proselintrc` will be checked if `$XDG_CONFIG_HOME/proselint/config` does not exist. ```json { "checks": { "typography.diacritical_marks": false } } ``` | ID | Description | | ----- | --------------- | | `airlinese.misc` | Avoiding jargon of the airline industry | | `annotations.misc` | Catching annotations left in the text | | `archaism.misc` | Avoiding archaic forms | | `cliches.hell` | Avoiding a common cliché | | `cliches.misc` | Avoiding clichés | | `consistency.spacing` | Consistent sentence spacing | | `consistency.spelling` | Consistent spelling | | `corporate_speak.misc` | Avoiding corporate buzzwords | | `cursing.filth` | Words to avoid | | `cursing.nfl` | Avoiding words banned by the NFL | | `dates_times.am_pm` | Using the right form for the time of day | | `dates_times.dates` | Stylish formatting of dates | | `hedging.misc` | Not hedging | | `hyperbole.misc` | Not being hyperbolic | | `jargon.misc` | Avoiding miscellaneous jargon | | `lgbtq.offensive_terms` | Avoding offensive LGBTQ terms | | `lgbtq.terms` | Misused LGBTQ terms | | `lexical_illusions.misc` | Avoiding lexical illusions | | `links.broken` | Linking only to existing sites | | `malapropisms.misc` | Avoiding common malapropisms | | `misc.apologizing` | Being confident | | `misc.back_formations` | Avoiding needless backformations | | `misc.bureaucratese` | Avoiding bureaucratese | | `misc.but` | Avoid starting a paragraph with "But..." | | `misc.capitalization` | Capitalizing only what ought to be capitalized | | `misc.chatspeak` | Avoiding lolling and other chatspeak | | `misc.commercialese` | Avoiding jargon of the commercial world | | `misc.currency` | Avoiding redundant currency symbols | | `misc.debased` | Avoiding debased language | | `misc.false_plurals` | Avoiding false plurals | | `misc.illogic` | Avoiding illogical forms | | `misc.inferior_superior` | Superior to, not than | | `misc.latin` | Avoiding overuse of Latin phrases | | `misc.many_a` | Many a singular | | `misc.metaconcepts` | Avoiding overuse of metaconcepts | | `misc.narcissism` | Talking about the subject, not its study | | `misc.phrasal_adjectives` | Hyphenating phrasal adjectives | | `misc.preferred_forms` | Miscellaneous preferred forms | | `misc.pretension` | Avoiding being pretentious | | `misc.professions` | Calling jobs by the right name | | `misc.punctuation` | Using punctuation assiduously | | `misc.scare_quotes` | Using scare quotes only when needed | | `misc.suddenly` | Avoiding the word suddenly | | `misc.tense_present` | Advice from Tense Present | | `misc.waxed` | Waxing poetic | | `misc.whence` | Using "whence" | | `mixed_metaphors.misc` | Not mixing metaphors | | `mondegreens.misc` | Avoiding mondegreen | | `needless_variants.misc` | Using the preferred form | | `nonwords.misc` | Avoid using nonwords | | `oxymorons.misc` | Avoiding oxymorons | | `psychology.misc` | Avoiding misused psychological terms | | `redundancy.misc` | Avoiding redundancy and saying things twice | | `redundancy.ras_syndrome` | Avoiding RAS syndrome | | `skunked_terms.misc` | Avoid using skunked terms | | `spelling.able_atable` | -able vs. -atable | | `spelling.able_ible` | -able vs. -ible | | `spelling.athletes` | Spelling of athlete names | | `spelling.em_im_en_in` | -em vs. -im and -en vs. -in | | `spelling.er_or` | -er vs. -or | | `spelling.in_un` | in- vs. un- | | `spelling.misc` | Spelling words corectly | | `security.credit_card` | Keeping credit card numbers secret | | `security.password` | Keeping passwords secret | | `sexism.misc` | Avoiding sexist language | | `terms.animal_adjectives` | Animal adjectives | | `terms.denizen_labels` | Calling denizens by the right name | | `terms.eponymous_adjectives` | Calling people by the right name | | `terms.venery` | Call groups of animals by the right name | | `typography.diacritical_marks` | Using dïacríticâl marks | | `typography.exclamation` | Avoiding overuse of exclamation | | `typography.symbols` | Using the right symbols | | `uncomparables.misc` | Not comparing uncomparables | | `weasel_words.misc` | Avoiding weasel words | | `weasel_words.very` | Avoiding the word "very" | ### Contributing Interested in contributing to `proselint`? Great — there are plenty of ways you can help. Read more on [our website], where we describe how you can help us build `proselint` into the greatest writing tool in the world. - [Issue Tracker](http://github.com/amperser/proselint/issues) - [Source Code](http://github.com/amperser/proselint) [our website]: http://proselint.com/contributing/ ### Support If you run into a problem, please [open an issue](http://github.com/amperser/proselint/issues) in or send an email to hello@amperser.com. ### Running Automated Tests Automated tests are included in the `proselint/tests` directory. To run these tests locally, you can use `./utils`. ### License The project is licensed under the BSD license. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/__init__.py0000644000000000000000000000017400000000000014633 0ustar00# flake8: noqa """Proselint applies advice from great writers to your writing.""" from . import tools __all__ = ("tools") ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/__main__.py0000644000000000000000000000021600000000000014611 0ustar00""" __main__.py. This lets you run python -m proselint. """ from .command_line import proselint if __name__ == '__main__': proselint() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/__init__.py0000644000000000000000000000010100000000000016061 0ustar00"""All the checks are organized into modules and places here.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/airlinese/__init__.py0000644000000000000000000000002100000000000020035 0ustar00"""Airlinese.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/airlinese/misc.py0000644000000000000000000000105700000000000017243 0ustar00"""Airlinese. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: Airlinese date: 2014-06-10 12:31:19 categories: writing --- Airlinese. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "airlinese.misc" msg = "'{}' is airlinese." airlinese = [ "enplan(?:e|ed|ing|ement)", "deplan(?:e|ed|ing|ement)", "taking off momentarily", ] return existence_check(text, airlinese, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/annotations/__init__.py0000644000000000000000000000002300000000000020421 0ustar00"""Annotations.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/annotations/misc.py0000644000000000000000000000117300000000000017624 0ustar00"""Annotation left in text. --- layout: post source: SublimeLinter-annotations source_url: http://bit.ly/16Q7H41 title: archaism date: 2014-06-10 12:31:19 categories: writing --- Annotation left in text. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "annotations.misc" msg = "Annotation left in text." annotations = [ "FIXME", "FIX ME", "TODO", "todo", "ERASE THIS", "FIX THIS", ] return existence_check( text, annotations, err, msg, ignore_case=False, join=True) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/archaism/__init__.py0000644000000000000000000000002000000000000017650 0ustar00"""Archaism.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/archaism/misc.py0000644000000000000000000000343600000000000017062 0ustar00"""Archaism. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: archaism date: 2014-06-10 12:31:19 categories: writing --- Archaism. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "archaism.misc" msg = "'{}' is archaic." archaisms = [ "alack", "anent", # "anon", "begat", "belike", "betimes", "boughten", "brocage", "brokage", "camarade", "chiefer", "chiefest", "Christiana", "completely obsolescent", "cozen", "divers", "deflexion", "durst", "fain", "forsooth", "foreclose from", "haply", "howbeit", "illumine", "in sooth", "maugre", "meseems", "methinks", "nigh", "peradventure", "perchance", "saith", "shew", "sistren", "spake", "to wit", "verily", "whilom", "withal", "wot", "enclosed please find", "please find enclosed", "enclosed herewith", "enclosed herein", "inforce", "ex postfacto", "foreclose from", "forewent", "for ever", # "designer", when used to mean a plotter against Christ # "demean", when used to mean "to behave" in legal contexts # "by the bye", # variant, modern is "by the by" # "comptroller" # in British English # "abortive" Abortive is archaic in reference to abortions of fetuses, # except in the sense “causing an abortion.” ] return existence_check(text, archaisms, err, msg, join=True) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/cliches/__init__.py0000644000000000000000000000002500000000000017500 0ustar00"""Avoid cliches.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/cliches/hell.py0000644000000000000000000000102100000000000016662 0ustar00"""Too much yelling. --- layout: post source: ??? source_url: ??? title: yelling date: 2014-06-10 12:31:19 categories: writing --- Never use the phrase 'all hell broke loose'. """ from proselint.tools import existence_check, max_errors, memoize @max_errors(1) @memoize def check_repeated_exclamations(text): """Check the text.""" err = "leonard.hell" msg = "Never use the words 'all hell broke loose'." regex = r"all hell broke loose" return existence_check(text, [regex], err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/cliches/misc.py0000644000000000000000000006136600000000000016713 0ustar00"""Cliches are cliché.""" from proselint.tools import existence_check, memoize @memoize def check_cliches_garner(text): """Check the text. source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY """ err = "cliches.garner" msg = "'{}' is cliché." cliches = [ "a fate worse than death", "alas and alack", "at the end of the day", "bald-faced lie", "between a rock and a hard place", "between Scylla and Charybdis", "between the devil and the deep blue sea", "betwixt and between", "blissful ignorance", "blow a fuse", "bulk large", "but that's another story", "cast aspersions", "chase a red herring", "comparing apples and oranges", "compleat", "conspicuous by its absence", "crystal clear", "cutting edge", "decision-making process", "dubious distinction", "duly authorized", "eyes peeled", "far be it from me", "fast and loose", "fills the bill", "first and foremost", "for free", "get with the program", "gilding the lily", "have a short fuse", "he's got his hands full", "his own worst enemy", "his work cut out for him", "hither and yon", "Hobson's choice", "horns of a dilemma", "if you catch my drift", "in light of", "in the final analysis", "in the last analysis", "innocent bystander", "it's not what you know, it's who you know", "last but not least", "make a mockery of", "male chauvinism", "moment of truth", "more in sorrow than in anger", "more sinned against than sinning", "my better half", "nip in the bud", "olden days", "on the same page", "presidential timber", "pulled no punches", "quantum jump", "quantum leap", "redound to one's credit", "redound to the benefit of", "sea change", "shirked his duties", "six of one, half a dozen of the other", "stretched to the breaking point", "than you can shake a stick at", "the cream of the crop", "the cream rises to the top", "the straw that broke the camel's back", "thick as thieves", "thinking outside the box", "thought leaders?", "throw the baby out with the bathwater", "various and sundry", "viable alternative", "wax eloquent", "wax poetic", "we've got a situation here", "whet (?:the|your) appetite", "wool pulled over our eyes", "writ large", ] return existence_check(text, cliches, err, msg, join=True) @memoize def check_cliches_write_good(text): """Check the text. source: write-good source_url: https://github.com/btford/write-good """ err = "cliches.write_good" msg = "'{}' is a cliché." cliches = [ "a chip off the old block", "a clean slate", "a dark and stormy night", "a far cry", "a fine kettle of fish", "a loose cannon", "a penny saved is a penny earned", "a tough row to hoe", "a word to the wise", "ace in the hole", "acid test", "add insult to injury", "against all odds", "air your dirty laundry", "all fun and games", "all in a day's work", "all talk, no action", "all thumbs", "all your eggs in one basket", "all's fair in love and war", "all's well that ends well", "almighty dollar", "American as apple pie", "an axe to grind", "another day, another dollar", "armed to the teeth", "as luck would have it", "as old as time", "as the crow flies", "at loose ends", "at my wits end", "avoid like the plague", "babe in the woods", "back against the wall", "back in the saddle", "back to square one", "back to the drawing board", "bad to the bone", "badge of honor", "bald faced liar", "ballpark figure", "banging your head against a brick wall", "baptism by fire", "barking up the wrong tree", "bat out of hell", "be all and end all", "beat a dead horse", "beat around the bush", "been there, done that", "beggars can't be choosers", "behind the eight ball", "bend over backwards", "benefit of the doubt", "bent out of shape", "best thing since sliced bread", "bet your bottom dollar", "better half", "better late than never", "better mousetrap", "better safe than sorry", "between a rock and a hard place", "beyond the pale", "bide your time", "big as life", "big cheese", "big fish in a small pond", "big man on campus", "bigger they are the harder they fall", "bird in the hand", "bird's eye view", "birds and the bees", "birds of a feather flock together", "bit the hand that feeds you", "bite the bullet", "bite the dust", "bitten off more than he can chew", "black as coal", "black as pitch", "black as the ace of spades", "blast from the past", "bleeding heart", "blessing in disguise", "blind ambition", "blind as a bat", "blind leading the blind", "blood is thicker than water", "blood sweat and tears", "blow off steam", "blow your own horn", "blushing bride", "boils down to", "bolt from the blue", "bone to pick", "bored stiff", "bored to tears", "bottomless pit", "boys will be boys", "bright and early", "brings home the bacon", "broad across the beam", "broken record", "brought back to reality", "bull by the horns", "bull in a china shop", "burn the midnight oil", "burning question", "burning the candle at both ends", "burst your bubble", "bury the hatchet", "busy as a bee", "by hook or by crook", "call a spade a spade", "called onto the carpet", "calm before the storm", "can of worms", "can't cut the mustard", "can't hold a candle to", "case of mistaken identity", "cat got your tongue", "cat's meow", "caught in the crossfire", "caught red-handed", "checkered past", "chomping at the bit", "cleanliness is next to godliness", "clear as a bell", "clear as mud", "close to the vest", "cock and bull story", "cold shoulder", "come hell or high water", "cool as a cucumber", "cool, calm, and collected", "cost a king's ransom", "count your blessings", "crack of dawn", "crash course", "creature comforts", "cross that bridge when you come to it", "crushing blow", "cry like a baby", "cry me a river", "cry over spilt milk", "crystal clear", "curiosity killed the cat", "cut and dried", "cut through the red tape", "cut to the chase", "cute as a bugs ear", "cute as a button", "cute as a puppy", "cuts to the quick", "dark before the dawn", "day in, day out", "dead as a doornail", "devil is in the details", "dime a dozen", "divide and conquer", "dog and pony show", "dog days", "dog eat dog", "dog tired", "don't burn your bridges", "don't count your chickens", "don't look a gift horse in the mouth", "don't rock the boat", "don't step on anyone's toes", "don't take any wooden nickels", "down and out", "down at the heels", "down in the dumps", "down the hatch", "down to earth", "draw the line", "dressed to kill", "dressed to the nines", "drives me up the wall", "dull as dishwater", "dyed in the wool", "eagle eye", "ear to the ground", "early bird catches the worm", "easier said than done", "easy as pie", "eat your heart out", "eat your words", "eleventh hour", "even the playing field", "every dog has its day", "every fiber of my being", "everything but the kitchen sink", "eye for an eye", "face the music", "facts of life", "fair weather friend", "fall by the wayside", "fan the flames", "feast or famine", "feather your nest", "feathered friends", "few and far between", "fifteen minutes of fame", "filthy vermin", "fine kettle of fish", "fish out of water", "fishing for a compliment", "fit as a fiddle", "fit the bill", "fit to be tied", "flash in the pan", "flat as a pancake", "flip your lid", "flog a dead horse", "fly by night", "fly the coop", "follow your heart", "for all intents and purposes", "for the birds", "for what it's worth", "force of nature", "force to be reckoned with", "forgive and forget", "fox in the henhouse", "free and easy", "free as a bird", "fresh as a daisy", "full steam ahead", "fun in the sun", "garbage in, garbage out", "gentle as a lamb", "get a kick out of", "get a leg up", "get down and dirty", "get the lead out", "get to the bottom of", "get your feet wet", "gets my goat", "gilding the lily", "give and take", "go against the grain", "go at it tooth and nail", "go for broke", "go him one better", "go the extra mile", "go with the flow", "goes without saying", "good as gold", "good deed for the day", "good things come to those who wait", "good time was had by all", "good times were had by all", "greased lightning", "greek to me", "green thumb", "green-eyed monster", "grist for the mill", "growing like a weed", "hair of the dog", "hand to mouth", "happy as a clam", "happy as a lark", "hasn't a clue", "have a nice day", "have high hopes", "have the last laugh", "haven't got a row to hoe", "head honcho", "head over heels", "hear a pin drop", "heard it through the grapevine", "heart's content", "heavy as lead", "hem and haw", "high and dry", "high and mighty", "high as a kite", "hit paydirt", "hold your head up high", "hold your horses", "hold your own", "hold your tongue", "honest as the day is long", "horns of a dilemma", "horse of a different color", "hot under the collar", "hour of need", "I beg to differ", "icing on the cake", "if the shoe fits", "if the shoe were on the other foot", "in a jam", "in a jiffy", "in a nutshell", "in a pig's eye", "in a pinch", "in a word", "in hot water", "in the gutter", "in the nick of time", "in the thick of it", "in your dreams", "it ain't over till the fat lady sings", "it goes without saying", "it takes all kinds", "it takes one to know one", "it's a small world", "it's only a matter of time", "ivory tower", "Jack of all trades", "jockey for position", "jog your memory", "joined at the hip", "judge a book by its cover", "jump down your throat", "jump in with both feet", "jump on the bandwagon", "jump the gun", "jump to conclusions", "just a hop, skip, and a jump", "just the ticket", "justice is blind", "keep a stiff upper lip", "keep an eye on", "keep it simple, stupid", "keep the home fires burning", "keep up with the Joneses", "keep your chin up", "keep your fingers crossed", "kick the bucket", "kick up your heels", "kick your feet up", "kid in a candy store", "kill two birds with one stone", "kiss of death", "knock it out of the park", "knock on wood", "knock your socks off", "know him from Adam", "know the ropes", "know the score", "knuckle down", "knuckle sandwich", "knuckle under", "labor of love", "ladder of success", "land on your feet", "lap of luxury", "last but not least", "last hurrah", "last-ditch effort", "law of the jungle", "law of the land", "lay down the law", "leaps and bounds", "let sleeping dogs lie", "let the cat out of the bag", "let the good times roll", "let your hair down", "let's talk turkey", "letter perfect", "lick your wounds", "lies like a rug", "life's a bitch", "life's a grind", "light at the end of the tunnel", "lighter than a feather", "lighter than air", "like clockwork", "like father like son", "like taking candy from a baby", "like there's no tomorrow", "lion's share", "live and learn", "live and let live", "long and short of it", "long lost love", "look before you leap", "look down your nose", "look what the cat dragged in", "looking a gift horse in the mouth", "looks like death warmed over", "loose cannon", "lose your head", "lose your temper", "loud as a horn", "lounge lizard", "loved and lost", "low man on the totem pole", "luck of the draw", "luck of the Irish", "make hay while the sun shines", "make money hand over fist", "make my day", "make the best of a bad situation", "make the best of it", "make your blood boil", "man of few words", "man's best friend", "mark my words", "meaningful dialogue", "missed the boat on that one", "moment in the sun", "moment of glory", "moment of truth", "money to burn", "more power to you", "more than one way to skin a cat", "movers and shakers", "moving experience", "naked as a jaybird", "naked truth", "neat as a pin", "needle in a haystack", "needless to say", "neither here nor there", "never look back", "never say never", "nip and tuck", "nip it in the bud", "no guts, no glory", "no love lost", "no pain, no gain", "no skin off my back", "no stone unturned", "no time like the present", "no use crying over spilled milk", "nose to the grindstone", "not a hope in hell", "not a minute's peace", "not in my backyard", "not playing with a full deck", "not the end of the world", "not written in stone", "nothing to sneeze at", "nothing ventured nothing gained", "now we're cooking", "off the top of my head", "off the wagon", "off the wall", "old hat", "older and wiser", "older than dirt", "older than Methuselah", "on a roll", "on cloud nine", "on pins and needles", "on the bandwagon", "on the money", "on the nose", "on the rocks", "on the spot", "on the tip of my tongue", "on the wagon", "on thin ice", "once bitten, twice shy", "one bad apple doesn't spoil the bushel", "one born every minute", "one brick short", "one foot in the grave", "one in a million", "one red cent", "only game in town", "open a can of worms", "open and shut case", "open the flood gates", "opportunity doesn't knock twice", "out of pocket", "out of sight, out of mind", "out of the frying pan into the fire", "out of the woods", "out on a limb", "over a barrel", "over the hump", "pain and suffering", "pain in the", "panic button", "par for the course", "part and parcel", "party pooper", "pass the buck", "patience is a virtue", "pay through the nose", "penny pincher", "perfect storm", "pig in a poke", "pile it on", "pillar of the community", "pin your hopes on", "pitter patter of little feet", "plain as day", "plain as the nose on your face", "play by the rules", "play your cards right", "playing the field", "playing with fire", "pleased as punch", "plenty of fish in the sea", "point with pride", "poor as a church mouse", "pot calling the kettle black", "pretty as a picture", "pull a fast one", "pull your punches", "pulling your leg", "pure as the driven snow", "put it in a nutshell", "put one over on you", "put the cart before the horse", "put the pedal to the metal", "put your best foot forward", "put your foot down", "quick as a bunny", "quick as a lick", "quick as a wink", "quick as lightning", "quiet as a dormouse", "rags to riches", "raining buckets", "raining cats and dogs", "rank and file", "rat race", "reap what you sow", "red as a beet", "red herring", "reinvent the wheel", "rich and famous", "rings a bell", "ripe old age", "ripped me off", "rise and shine", "road to hell is paved with good intentions", "rob Peter to pay Paul", "roll over in the grave", "rub the wrong way", "ruled the roost", "running in circles", "sad but true", "sadder but wiser", "salt of the earth", "scared stiff", "scared to death", "sealed with a kiss", "second to none", "see eye to eye", "seen the light", "seize the day", "set the record straight", "set the world on fire", "set your teeth on edge", "sharp as a tack", "shoot for the moon", "shoot the breeze", "shot in the dark", "shoulder to the wheel", "sick as a dog", "sigh of relief", "signed, sealed, and delivered", "sink or swim", "six of one, half a dozen of another", "skating on thin ice", "slept like a log", "slinging mud", "slippery as an eel", "slow as molasses", "smart as a whip", "smooth as a baby's bottom", "sneaking suspicion", "snug as a bug in a rug", "sow wild oats", "spare the rod, spoil the child", "speak of the devil", "spilled the beans", "spinning your wheels", "spitting image of", "spoke with relish", "spread like wildfire", "spring to life", "squeaky wheel gets the grease", "stands out like a sore thumb", "start from scratch", "stick in the mud", "still waters run deep", "stitch in time", "stop and smell the roses", "straight as an arrow", "straw that broke the camel's back", "strong as an ox", "stubborn as a mule", "stuff that dreams are made of", "stuffed shirt", "sweating blood", "sweating bullets", "take a load off", "take one for the team", "take the bait", "take the bull by the horns", "take the plunge", "takes one to know one", "takes two to tango", "the more the merrier", "the real deal", "the real McCoy", "the red carpet treatment", "the same old story", "there is no accounting for taste", "thick as a brick", "thick as thieves", "thin as a rail", "think outside of the box", "third time's the charm", "this day and age", "this hurts me worse than it hurts you", "this point in time", "three sheets to the wind", "through thick and thin", "throw in the towel", "tie one on", "tighter than a drum", "time and time again", "time is of the essence", "tip of the iceberg", "tired but happy", "to coin a phrase", "to each his own", "to make a long story short", "to the best of my knowledge", "toe the line", "tongue in cheek", "too good to be true", "too hot to handle", "too numerous to mention", "touch with a ten foot pole", "tough as nails", "trial and error", "trials and tribulations", "tried and true", "trip down memory lane", "twist of fate", "two cents worth", "two peas in a pod", "ugly as sin", "under the counter", "under the gun", "under the same roof", "under the weather", "until the cows come home", "unvarnished truth", "up the creek", "uphill battle", "upper crust", "upset the applecart", "vain attempt", "vain effort", "vanquish the enemy", "vested interest", "waiting for the other shoe to drop", "wakeup call", "warm welcome", "watch your p's and q's", "watch your tongue", "watching the clock", "water under the bridge", "weather the storm", "weed them out", "week of Sundays", "went belly up", "wet behind the ears", "what goes around comes around", "what you see is what you get", "when it rains, it pours", "when push comes to shove", "when the cat's away", "when the going gets tough, the tough get going", "white as a sheet", "whole ball of wax", "whole hog", "whole nine yards", "wild goose chase", "will wonders never cease?", "wisdom of the ages", "wise as an owl", "wolf at the door", "words fail me", "work like a dog", "world weary", "worst nightmare", "worth its weight in gold", "wrong side of the bed", "yanking your chain", "yappy as a dog", "years young", "you are what you eat", "you can run but you can't hide", "you only live once", "you're the boss ", "young and foolish", "young and vibrant", ] return existence_check(text, cliches, err, msg, join=True) @memoize def check_cliches_gnu_diction(text): """Check the text. source: GNU diction source_url: https://directory.fsf.org/wiki/Diction """ err = "cliches.gnu_diction" msg = "'{}' is a cliché." list = [ "a matter of concern", "all things being equal", "as a last resort", "attached hereto", "by no means", "conspicuous by its absence", "easier said than done", "enclosed herewith", "if and when", "in reference to", "in short supply", "in the foreseeable future", "in the long run", "in the matter of", "it stands to reason", "many and diverse", "on the right track", "par for the course", "please feel free to", "pursuant to your request", "regarding the matter of", "slowly but surely", "this will acknowledge", "we are pleased to advice", "we regret to inform you", "we wish to state", "you are hereby advised that", ] return existence_check(text, list, err, msg, join=True, ignore_case=True) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/consistency/__init__.py0000644000000000000000000000004200000000000020426 0ustar00"""Various consistency checks.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/consistency/spacing.py0000644000000000000000000000120200000000000020312 0ustar00"""Mixed one vs. two spaces after a period. --- layout: post source: Consistency. source_url: ??? title: Mixed use of 1 vs. 2 spaces after a period. date: 2014-06-10 12:31:19 categories: writing --- Points out instances where there are two conventions, 1 vs. 2 spaces after a period, in the same document. """ from proselint.tools import consistency_check, memoize @memoize def check(text): """Check the text.""" err = "consistency.spacing" msg = "Inconsistent spacing after period (1 vs. 2 spaces)." regex = [r"[\.\?!] [A-Z]", r"[\.\?!] [A-Z]"] return consistency_check(text, [regex], err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/consistency/spelling.py0000644000000000000000000000265200000000000020515 0ustar00"""Inconsistent spelling. --- layout: post source: Intelligent Editing Ltd. source_url: http://bit.ly/1x3hYj7 title: Inconsistent spelling date: 2014-06-10 12:31:19 categories: writing --- Intelligent Editing Ltd. says: > Some words have more than one correct spelling. American, British, Australian and Canadian English all have their own preferences. Even within those, there can be multiple spellings. For example, in the UK 'realise' is often preferred. However, 'realize' has been used in British-English for centuries and is preferred in the Oxford English Dictionary. However, no matter which spelling is preferred, one thing is always wrong: you mustn't use two different spellings in the same document. """ from proselint.tools import consistency_check, memoize @memoize def check(text): """Check the text.""" err = "consistency.spelling" msg = "Inconsistent spelling of '{}' (vs. '{}')." word_pairs = [ ["advisor", "adviser"], # ["analyse", "analyze"], ["centre", "center"], ["colour", "color"], ["emphasise", "emphasize"], ["finalise", "finalize"], ["focussed", "focused"], ["labour", "labor"], ["learnt", "learned"], ["organise", "organize"], ["organised", "organized"], ["organising", "organizing"], ["recognise", "recognize"], ] return consistency_check(text, word_pairs, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/corporate_speak/__init__.py0000644000000000000000000000002700000000000021251 0ustar00"""Corporate-speak.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/corporate_speak/misc.py0000644000000000000000000000236300000000000020452 0ustar00"""Corporate speak. --- layout: post source: Travis Bradberry for Inc.com source_url: http://bit.ly/1IxWnto title: corporate speak date: 2014-06-10 12:31:19 categories: writing --- Avoid these cases of business jargon. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "corporate_speak.misc" msg = "Minimize your use of corporate catchphrases like this one." list = [ "at the end of the day", "back to the drawing board", "hit the ground running", "get the ball rolling", "low-hanging fruit", "thrown under the bus", "think outside the box", "let's touch base", "get my manager's blessing", "it's on my radar", "ping me", "i don't have the bandwidth", "no brainer", "par for the course", "bang for your buck", "synergy", "move the goal post", "apples to apples", "win-win", "circle back around", "all hands on deck", "take this offline", "drill-down", "elephant in the room", "on my plate", ] return existence_check(text, list, err, msg, ignore_case=True) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/cursing/__init__.py0000644000000000000000000000001700000000000017541 0ustar00"""Cursing.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/cursing/filth.py0000644000000000000000000000125500000000000017115 0ustar00"""Filthy words. --- layout: post source: George Carlin source_url: https://youtu.be/kyBH5oNQOS0 title: filthy words date: 2014-06-10 12:31:19 categories: writing --- Filthy words. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "cursing.filth" msg = """Nobody ever tells you this as a kid, but you're supposed to avoid this word.""" list = [ "shit", "piss", "fuck", "cunt", "cocksucker", "motherfucker", "tits", "fart", "turd", "twat", ] return existence_check(text, list, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/cursing/nfl.py0000644000000000000000000005717500000000000016602 0ustar00"""Words the NFL won't print on a jersey. --- layout: post source: The National Football League source_url: http://bit.ly/1ISK0rb title: words the NFL won't print on a jersey date: 2014-06-10 12:31:19 categories: writing --- Words the NFL won't print on a jersey. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "cursing.nfl" msg = "The NFL won't print this word on a jersey." list = [ "420", "666", "2 on 1", "3rd eye", "3rd leg", "3rdeye", "3rdleg", "3some", "4 twenty", "4twenty", "60 nine", "60nine", "a.s.s.", "anal", "anal annie", "anal sex", "analannie", "analsex", "anus", "arse", "ass", "ass bagger", "ass blaster", "ass clown", "ass cowboy", "ass fuck", "ass fucker", "ass hole", "ass holes", "ass hore", "ass jockey", "ass kiss", "ass kisser", "ass klown", "ass lick", "ass licker", "ass lover", "ass man", "ass monkey", "ass munch", "ass muncher", "ass packer", "ass pirate", "ass puppies", "ass ranger", "ass whore", "ass wipe", "assbagger", "assblaster", "assclown", "asscowboy", "assfuck", "assfucker", "asshole", "assholes", "asshore", "assjockey", "asskiss", "asskisser", "assklown", "asslick", "asslicker", "asslover", "assman", "assmonkey", "assmunch", "assmuncher", "asspacker", "asspirate", "asspuppies", "assranger", "asswhore", "asswipe", "athletes foot", "athletesfoot", "axing the weasel", "b hard", "back door", "back door man", "backdoor", "backdoorman", "backseat", "bad ass", "bad fuck", "badfuck", "ball licker", "ball sack", "balllicker", "balls", "ballsack", "banging", "barely legal", "barelylegal", "barf", "barf face", "barface", "barfface", "bastard", "bazongas", "bazooms", "beastality", "beastiality", "beat off", "beat your meat", "beatoff", "beat-off", "beatyourmeat", "bi", "bi sexual", "biatch", "big ass", "big bitch", "big bitch", "big butt", "bigass", "bigbastard", "bigbutt", "bisexual", "bi-sexual", "bitch", "bitches", "bitchin", "bitchy", "bite me", "biteme", "black out", "blackout", "blow job", "blowjob", "bm", "boner", "bong", "boobies", "boobs", "boody", "breast", "breast job", "breast lover", "breast man", "breastjob", "breastlover", "breastman", "budweiser", "bull crap", "bull dike", "bull dyke", "bull shit", "bullcrap", "bulldike", "bulldyke", "bullshit", "bumble fuck", "bumblefuck", "bumfuck", "bunghole", "butch babes", "butch dike", "butch dyke", "butchbabes", "butchdike", "butchdyke", "butt bang", "butt fuck", "butt fucker", "butt fuckers", "butt head", "butt man", "butt plug", "butt stain", "buttbang", "butt-bang", "buttface", "buttfuck", "butt-fuck", "buttfucker", "butt-fucker", "buttfuckers", "butt-fuckers", "butthead", "buttman", "buttpirate", "buttplug", "buttstain", "camel toe", "cameltoe", "carpet muncher", "carpetmuncher", "carruth", "cherry popper", "cherrypopper", "chick slick", "chickslick", "clam digger", "clam diver", "clamdigger", "clamdiver", "clit", "clitoris", "cock", "cock block", "cock blocker", "cock cowboy", "cock fight", "cock knob", "cock licker", "cock lover", "cock nob", "cock queen", "cock rider", "cock smith", "cock sucker", "cock tail", "cock tease", "cockblock", "cockblocker", "cockcowboy", "cockfight", "cockhead", "cockknob", "cocklicker", "cocklover", "cocknob", "cockqueen", "cockrider", "cocks man", "cocksman", "cocksmith", "cocksucer", "cocksucker", "cocktail", "cocktease", "cocky", "condom", "copulate", "corn hole", "cornhole", "crabs", "crack", "crack pipe", "crack whore", "crackpipe", "crackwhore", "crack-whore", "crap", "crappy", "creamy", "crotch", "crotch jockey", "crotch monkey", "crotch rot", "crotchjockey", "crotchmonkey", "crotchrot", "cum", "cum bubble", "cum fest", "cum jockey", "cum quat", "cum queen", "cum shot", "cumbubble", "cumfest", "cumjockey", "cumm", "cumming", "cumquat", "cumqueen", "cumshot", "cunnilingus", "cunt", "cunt fuck", "cunt fucker", "cunt licker", "cuntfuck", "cuntfucker", "cuntlicker", "cyber sex", "cyber slimer", "cybersex", "cyberslimer", "dahmer", "damn", "damn it", "damnit", "datnigga", "dd", "deap throat", "deaper", "deapthroat", "deep throat", "deeper", "deepthroat", "defecate", "deposit", "devil", "dick brain", "dick fart", "dick for brains", "dick head", "dick lick", "dick licker", "dick likcer", "dick wad", "dick weed", "dickbrain", "dickforbrains", "dickhead", "dickless", "dicklick", "dicklicker", "dickman", "dickwad", "dickweed", "dike", "dildo", "dip stick", "dipstick", "dirty ho", "dix", "dixie dike", "dixie dyke", "dixiedike", "dixiedyke", "do me", "doggie style", "doggiestyle", "doggy stlye", "doggystyle", "dome", "dong", "dope", "double d", "doubled", "drag queen", "dragqueen", "dragqween", "dre", "drip dick", "dripdick", "drunk", "drunken", "dumb ass", "dumb bitch", "dumb fuck", "dumbass", "dumbbitch", "dumbfuck", "easy slut", "easyslut", "eat me", "eat pussy", "eatballs", "eatme", "eatpussy", "ejaculate", "erection", "evl", "excrement", "f toyota", "f.i.n.e.", "f.u.c.k.", "face fucker", "facefucker", "faggot", "fagot", "fairy", "fanny fucker", "fannyfucker", "fart", "fast fuck", "fastfuck", "fat ass", "fat fuck", "fat fucker", "fatass", "fatfuck", "fatfucker", "fatso", "fellatio", "femme", "finger food", "finger fuck", "finger fucker", "fingerfood", "fingerfuck", "fingerfucker", "fist fuck", "fist fucker", "fistfuck", "fistfucker", "fisting", "flasher", "flatulence", "floggin the dolphin", "fondle", "foot fuck", "foot fucker", "foot licker", "footaction", "footfuck", "footfucker", "footlicker", "footstar", "fore skin", "foreskin", "fornicate", "four 20", "four twenty", "four20", "fourtwenty", "freak fuck", "freakfuck", "freaky fucker", "freakyfucker", "free 4 all", "free for all", "free fuck", "free4all", "freeforall", "freefuck", "fuck", "fuck bag", "fuck buddy", "fuck face", "fuck fest", "fuck freak", "fuck friend", "fuck head", "fuck her", "fuck it", "fuck knob", "fuck me", "fuck me hard", "fuck monkey", "fuck off", "fuck pig", "fuck them", "fuck whore", "fuck you", "fucka", "fuckable", "fuckbag", "fuckbuddy", "fucked", "fucked up", "fuckedup", "fucker", "fuckers", "fuckface", "fuckfest", "fuckfreak", "fuckfriend", "fuckhead", "fuckher", "fuckin", "fuckin a", "fuckin nuts", "fuckin right", "fuckina", "fucking", "fucking a", "fucking bitch", "fucking nuts", "fuckingbitch", "fuckinnuts", "fuckinright", "fuckit", "fuckknob", "fuckme", "fuckmehard", "fuckmonkey", "fuckoff", "fuckpig", "fuckwhore", "fuckyou", "fudge pakcers", "fun fuck", "funfuck", "fuuck", "g unit", "gang bang", "gang banger", "gangbang", "gangbanger", "gay", "gay ass", "gay mutha fuckin queer", "gay pride", "gaymuthafuckinwhore", "genital", "get it on", "getiton", "giehn", "give head", "givehead", "glazed donut", "glazeddonut", "go me", "go to hell", "god", "god damed mutha fucka", "god damit", "god damn", "god damned", "god manit", "goddamit", "goddamn", "goddamned", "goddamnes", "goddamnit", "goddamnmuthafucker", "gonorrehea", "gonzagas", "gook", "got jesus", "got2haveit", "gotohell", "g-unit", "hand job", "handjob", "hard on", "harder", "hardon", "harem", "he hate me", "head fuck", "head lights", "headfuck", "headlights", "hehateme", "hell", "hell no", "hell yes", "hellno", "hellyes", "hen house", "henhouse", "herpes", "hershey hi way", "hersheyhighway", "hersheyhiway", "hershy high way", "ho", "ho mo", "hobo", "hole", "hole stuffer", "holestuffer", "homo", "homo bangers", "homo sexual", "homobangers", "homosexual", "honkers", "honkey", "hooker", "hookers", "hooters", "hore", "horney", "horny", "horseshit", "hose job", "hosejob", "hoser", "hostage", "hot damn", "hot pussy", "hot to trot", "hot2trot", "hotdamn", "hotpussy", "hottotrot", "hussy", "hustler", "i love beer", "i luv beer", "id ten t", "id10t", "idiot", "idoit", "in the ass", "in the buff", "ingin", "insest", "inter course", "inter racial", "intercourse", "interracial", "intheass", "inthebuff", "jack the ripper", "jackass", "jackoff", "jacktheripper", "jap", "jap crap", "japcrap", "jerk off", "jerkoff", "jesus chirst", "jesuschrist", "jism", "jiz", "jiz juice", "jizim", "jizjuice", "jizz", "jizzim", "joint", "juggalo", "jugs", "k mart", "kill", "killer", "killing", "kiss ass", "kissass", "kkk", "kmart", "knockers", "koon", "kotex", "krap", "krappy", "kum", "kum bubble", "kum quat", "kumbubble", "kumbullbe", "kumquat", "kunt", "ky", "ky jelly", "lactate", "lady boog", "laid", "lap dance", "lapdance", "lesbain", "lesbayn", "lesbian", "lesbin", "lesbo", "lez", "lez be", "lez be friends", "lezbe", "lezbefriends", "lezbo", "lezz", "lezzo", "lick me", "licker", "lickme", "limp dick", "limpdick", "limy", "live sex", "livesex", "loaded gun", "loadedgun", "lolita", "looser", "lotion", "love bone", "love goo", "love gun", "love juice", "love muscle", "love pistol", "love rocket", "lovebone", "lovegoo", "lovegun", "lovejuice", "lovemuscle", "lovepistol", "loverocket", "low life", "lowlife", "lube job", "lubejob", "lucky camel toe", "luckycammeltoe", "magic wand", "magicwand", "mams", "man hater", "man paste", "manhater", "manpaste", "mary jane", "maryjane", "mastabate", "mastabater", "master blaster", "masterbate", "masterblaster", "mastrabator", "mattress princess", "mattressprincess", "meat beatter", "meatbeatter", "molest", "molester", "molestor", "money shot", "moneyshot", "mother fucker", "mother love bone", "motherfuck", "motherfucker", "motherlovebone", "muff", "muff dive", "muff diver", "muff licker", "muffdive", "muffdiver", "muffin diver", "muffindiver", "mufflikcer", "murder", "mutha fucker", "naked", "nasty bitch", "nasty ho", "nasty slut", "nasty whore", "nastybitch", "nastyho", "nastyslut", "nastywhore", "neon deon", "nig", "niger", "nigga", "nigger", "nipple", "nipple ring", "nipplering", "nit tit", "nittit", "no fucking way", "no sex", "nofuckingway", "nookie", "nooner", "nude", "nut fucker", "nutfucker", "oicu812", "on the rag", "ontherag", "orgasm", "orgy", "ou812", "oui", "p i m p", "pearl necklace", "pearlnecklace", "pecker", "pee", "peep show", "peepshow", "peepshpw", "penetration", "penis", "penthouse", "period", "phque", "pimp", "pimp simp", "pimped", "pimper", "pimpjuic", "pimpjuice", "pimpsimp", "piss", "piss head", "pissed", "pisser", "pisshead", "play boy", "play girl", "playboy", "playgirl", "pocket pool", "pocketpool", "polack", "poon tang", "poontang", "poop", "pooper", "poor white trash", "poorwhitetrash", "popimp", "porch monkey", "porchmonkey", "porn", "porn flick", "porn king", "porn princess", "pornflick", "pornking", "porno", "pornprincess", "pot", "premature", "prick", "prick head", "prickhead", "primetime", "prostitute", "pubic", "pubic lice", "pubiclice", "pud", "pud boy", "pudboy", "pudd", "pudd boy", "puddboy", "pun tang", "puntang", "purina princess", "purinapricness", "pussy", "pussy cat", "pussy eater", "pussy fucker", "pussy licker", "pussy lips", "pussy lover", "pussy pounder", "pussycat", "pussyeater", "pussyfucker", "pussylicker", "pussylips", "pussylover", "pussypounder", "putt pirate", "pwt", "queef", "queer", "quickie", "rae carruth", "rape", "rapist", "rear end", "rear entry", "rearend", "rearentry", "rectum", "red light", "redlight", "reefer", "rent a fuck", "rentafuck", "retard", "retarded", "ribbed", "rim job", "rimjob", "roach", "robber", "s and m", "s&m", "samckdaddy", "sandm", "satan", "schlong", "screw", "screw you", "screwyou", "scrotum", "semen", "sex", "sex farm", "sex hound", "sex house", "sex kitten", "sex pot", "sex slave", "sex to go", "sex toy", "sex toys", "sex whore", "sexfarm", "sexhound", "sexhouse", "sexkitten", "sexpot", "sexslave", "sextogo", "sextoy", "sextoys", "sexual", "sexwhore", "sexy", "sexy biatch", "sexy bitch", "sexy moma", "sexy slim", "sexymoma", "sexy-slim", "shag", "shaggin", "shagging", "shawtypimp", "shit", "shit dick", "shit eater", "shit face", "shit for brains", "shit fuck", "shit fucker", "shit happens", "shit head", "shit out of luck", "shit stain", "shit4brains", "shitdick", "shiteater", "shitface", "shitforbrains", "shitfuck", "shitfucker", "shithapens", "shithappens", "shithead", "shitoutofluck", "shits", "shitstain", "shitter", "shitting", "shitty", "short fuck", "shortfuck", "showtime", "six six six", "sixsixsix", "sixty 9", "sixty nine", "sixty9", "sixtynine", "skank", "skank bitch", "skank fuck", "skank whore", "skankbitch", "skankfuck", "skankwhore", "skanky bitch", "skanky whore", "skankybitch", "skankywhore", "skin flute", "skinflute", "skum", "skum bag", "skumbag", "slant", "slant eye", "slanteye", "slave", "slave driver", "slavedriver", "sleeze bag", "sleeze ball", "sleezebag", "sleezeball", "slide it in", "slideitin", "slime", "slime ball", "slime bucket", "slimeball", "slimebucket", "slut", "slut wear", "slut whore", "sluts", "slutt", "slutting", "slutty", "slutwear", "slutwhore", "smack daddy", "smack the monkey", "smackthemonkey", "smagma", "smart ass", "snatch", "snatch patch", "snatchpatch", "sniper", "snot", "sob", "sodomite", "sodomy", "son of a bitch", "sonofabitch", "sonofbitch", "spank the monkey", "spankthemonkey", "sperm", "sperm bag", "sperm hearder", "sperm herder", "spermacide", "spermbag", "spermhearder", "spermherder", "spic", "spick", "spit", "spitter", "split tail", "split tial", "splittail", "stagg", "strap on", "strapon", "stringer", "strip club", "stripclub", "stroke", "stroking", "stupid", "stupid fuck", "stupid fucker", "stupidfuck", "stupidfucker", "suck", "suck dick", "suck me", "suck my ass", "suck my dick", "suck my tit", "suck off", "suckdick", "sucker", "suckme", "suckmyass", "suckmydick", "suckmytit", "suckoff", "suicide", "swallow", "swallower", "swalow", "sweetness", "swign dixx", "swing dixx", "swingin dixx", "swinging dicks", "syphilis", "tampon", "tang", "testicle", "testicles", "third eye", "third leg", "thirdeye", "thirdleg", "three some", "threesome", "tit", "tit bit nipply", "tit fuck", "tit fucker", "tit fuckin", "tit job", "tit licker", "tit lover", "titbitnipply", "titfuck", "titfucker", "titfuckin", "titjob", "titlicker", "titlover", "tits", "titties", "titty", "toilet", "toilet bowl", "tongethruster", "tongue", "tongue thruster", "tongue tramp", "tonguethrust", "tonguetramp", "toung thruster", "tounge baller", "tounge thrust", "trailer trash", "trailertrash", "tramp", "tri sexual", "triple x", "triplex", "trisexual", "trojan", "trots", "tunnel of love", "tunneloflove", "turd", "two bit whore", "two on one", "twobitwhore", "unfuckable", "up the ass", "up the butt", "upskirt", "uptheass", "upthebutt", "urinate", "urine", "uterus", "vagina", "vaginal", "vd", "vibrater", "vibrator", "virgin", "virgin breaker", "virginbreaker", "vulva", "waysted", "weenie", "wet spot", "wetspot", "whacker", "whiskey dick", "whiskeydick", "whisky dick", "whiskydick", "white trash", "whitetrash", "whore", "whore fucker", "whore house", "whorefucker", "whorehouse", "wigger", "willie wanker", "williewanker", "wuutang", "xxx", "yellow man", "yellowman" ] return existence_check(text, list, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/cursing/nword.py0000644000000000000000000000104500000000000017135 0ustar00"""On 'the N word'. --- layout: post source: Louis CK source_url: https://youtu.be/dF1NUposXVQ?t=30s title: the 'n-word' date: 2014-06-10 12:31:19 categories: writing --- Take responsibility with the shitty words you wanna say. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "cursing.nword" msg = "Take responsibility for the shitty words you want to say." list = [ "the n-word", ] return existence_check(text, list, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/dates_times/__init__.py0000644000000000000000000000002700000000000020371 0ustar00"""Dates and times.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/dates_times/am_pm.py0000644000000000000000000000271700000000000017733 0ustar00"""a.m. / p.m. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: a.m. & p.m. date: 2014-06-10 12:31:19 categories: writing --- """ from proselint.tools import existence_check, memoize @memoize def check_lowercase_periods(text): """Check the text.""" err = "dates_times.am_pm.lowercase_periods" msg = "With lowercase letters, the periods are standard." return existence_check(text, [r"\d{1,2} ?[ap]m"], err, msg, ignore_case=False) @memoize def check_spacing(text): """Check the text.""" err = "dates_times.am_pm.spacing" msg = "It's standard to put a space before 'a.m.' or 'p.m.'." return existence_check(text, [r"\d{1,2}[ap]\.?m\.?"], err, msg) @memoize def check_midnight_noon(text): """Check the text.""" err = "dates_times.am_pm.midnight_noon" msg = ("12 a.m. and 12 p.m. are wrong and confusing." " Use 'midnight' or 'noon'.") return existence_check(text, [r"12 ?[ap]\.?m\.?"], err, msg) @memoize def check_redundancy(text): """Check the text.""" err = "dates_times.am_pm.midnight_noon" msg = ("'a.m.' is always morning; 'p.m.' is always night.") list = [ r"\d{1,2} ?a\.?m\.? in the morning", r"\d{1,2} ?p\.?m\.? in the evening", r"\d{1,2} ?p\.?m\.? at night", r"\d{1,2} ?p\.?m\.? in the afternoon", ] return existence_check(text, list, err, msg, join=True) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/dates_times/dates.py0000644000000000000000000000321300000000000017732 0ustar00"""Dates. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: dates date: 2014-06-10 12:31:19 categories: writing --- Dates. """ import calendar from proselint.tools import existence_check, memoize @memoize def check_decade_apostrophes_short(text): """Check the text for dates of the form X0's.""" err = "dates_times.dates" msg = "Apostrophes aren't needed for decades." regex = r"\d0\'s" return existence_check( text, [regex], err, msg, excluded_topics=["50 Cent"]) @memoize def check_decade_apostrophes_long(text): """Check the text for dates of the form XXX0's.""" err = "dates_times.dates" msg = "Apostrophes aren't needed for decades." regex = r"\d\d\d0\'s" return existence_check(text, [regex], err, msg) @memoize def check_dash_and_from(text): """Check the text.""" err = "dates_times.dates" msg = "When specifying a date range, write 'from X to Y'." regex = r"[fF]rom \d+[^ \t\n\r\f\va-zA-Z0-9_\.]\d+" return existence_check(text, [regex], err, msg) def check_month_year_comma(text): """Check the text.""" err = "dates_times.dates" msg = "When specifying a month and year, no comma is needed." regex = r"(?:" + "|".join(calendar.month_name[1:]) + r"), \d{3,}" return existence_check(text, [regex], err, msg) @memoize def check_month_of_year(text): """Check the text.""" err = "dates_times.dates" msg = "When specifying a month and year, 'of' is unnecessary." regex = r"(?:" + "|".join(calendar.month_name[1:]) + r") of \d{3,}" return existence_check(text, [regex], err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/hedging/__init__.py0000644000000000000000000000001700000000000017474 0ustar00"""Hedging.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/hedging/misc.py0000644000000000000000000000103000000000000016664 0ustar00"""Hedging. --- layout: post source: Pinker's book on writing source_url: ??? title: hedging date: 2014-06-10 12:31:19 categories: writing --- Points out hedging. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Suggest the preferred forms.""" err = "hedging.misc" msg = "Hedging. Just say it." narcissism = [ "I would argue that", ", so to speak", "to a certain degree", ] return existence_check(text, narcissism, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/hyperbole/__init__.py0000644000000000000000000000002100000000000020053 0ustar00"""Hyperbole.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/hyperbole/misc.py0000644000000000000000000000074200000000000017261 0ustar00"""Hyperbolic language. --- layout: post source: ??? source_url: ??? title: hyperbolic language date: 2014-06-10 12:31:19 categories: writing --- Hyperbolic language. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "hyperbolic.misc" msg = "'{}' is hyperbolic." words = [ r"[a-z]*[!]{2,}", r"[a-z]*\?{2,}" ] return existence_check(text, words, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/inprogress/capitalization_errors.py0000644000000000000000000000131500000000000023134 0ustar00"""Password in plain text. --- layout: post source: ??? source_url: ??? title: Capitalization of abbreviations date: 2014-06-10 12:31:19 categories: writing --- In Hybrid Zones, p 255 in a citation Hughes & Huges Systems Experts and Computers: The Systems Approach in Management and Engineering: World War Ii and After. World War Ii should have correct capitalization. """ from proselint.tools import blacklist, memoize @memoize def check(text): """Check the text.""" err = "MSC104" msg = "Don't fail to capitalize roman numeral abbreviations." pwd_regex = " (I(i*)|i*)" password = [ f"World War{pwd_regex}", ] return blacklist(text, password, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/inprogress/example_check.py0000644000000000000000000000067400000000000021324 0ustar00"""First line is always wrong. --- layout: post source: Nobody source_url: ??? title: Firse line is always wrong. date: 2014-06-10 12:31:19 categories: writing --- The first line always is always wrong. """ from proselint.tools import reverse def check(text): """Check the text.""" error_code = "example.first" msg = "First line always has an error." reverse(text) return [(1, 1, error_code, msg)] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/jargon/__init__.py0000644000000000000000000000001600000000000017346 0ustar00"""Jargon.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/jargon/misc.py0000644000000000000000000000123100000000000016542 0ustar00"""Cliches. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: a vs. an date: 2014-06-10 12:31:19 categories: writing --- Cliches are cliché. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "jargon.misc" msg = "'{}' is jargon. Can you replace it with something more standard?" jargon = [ "in the affirmative", "in the negative", "agendize", "per your order", "per your request", "disincentivize", ] return existence_check(text, jargon, err, msg, join=True) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/lexical_illusions/__init__.py0000644000000000000000000000003100000000000021605 0ustar00"""Lexical illusions.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/lexical_illusions/misc.py0000644000000000000000000000136500000000000021014 0ustar00"""Lexical illusions. --- layout: post source: write-good source_url: https://github.com/btford/write-good title: Lexical illusion present date: 2014-06-10 12:31:19 categories: writing --- A lexical illusion is when a word word is unintentionally repeated twice, and and this happens most often between line breaks. """ from proselint.tools import existence_check, memoize @memoize 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" exceptions = [r"^had had$", r"^that that$"] return existence_check(text, [regex], err, msg, exceptions=exceptions, require_padding=False) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/lgbtq/__init__.py0000644000000000000000000000001500000000000017176 0ustar00"""GLAAD.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/lgbtq/offensive_terms.py0000644000000000000000000000207300000000000020643 0ustar00"""GLAAD. --- layout: post source: GLAAD Media Reference Guide - 9th Edition source_url: http://www.glaad.org/reference title: GLAAD Guidelines date: 2016-07-06 categories: writing --- This check looks for offensive terms related to LGBTQ issues and raises an error marking them as offensive. The New York Times and Associated Press have also adopted this style guide. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Flag offensive words based on the GLAAD reference guide.""" err = "glaad.offensive_terms" msg = "Offensive term. Remove it or consider the context." list = [ "fag", "faggot", "dyke", "sodomite", "homosexual agenda", "gay agenda", "transvestite", "homosexual lifestyle", "gay lifestyle" # homo - may create false positives without additional context # FIXME use topic detetor to decide whether "homo" is offensive ] return existence_check(text, list, err, msg, join=True, ignore_case=False) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/lgbtq/terms.py0000644000000000000000000000234500000000000016601 0ustar00"""GLAAD. --- layout: post source: GLAAD Media Reference Guide - 9th Edition source_url: http://www.glaad.org/reference title: GLAAD Guidelines date: 2016-07-06 categories: writing --- This check looks for possibly offensive terms related to LGBTQ issues and makes more acceptable recommendations. TheNew York Times and Associated Press have also adopted this style guide. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest preferred forms given the reference document.""" err = "glaad.terms" msg = "Possibly offensive term. Consider using '{}' instead of '{}'." list = [ ["gay man", ["homosexual man"]], ["gay men", ["homosexual men"]], ["lesbian", ["homosexual woman"]], ["lesbians", ["homosexual women"]], ["gay people", ["homosexual people"]], ["gay couple", ["homosexual couple"]], ["sexual orientation", ["sexual preference"]], ["openly gay", ["admitted homosexual", "avowed homosexual"]], ["equal rights", ["special rights"]] ] return preferred_forms_check(text, list, err, msg, ignore_case=False) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/links/__init__.py0000644000000000000000000000002400000000000017205 0ustar00"""Broken links.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/links/broken.py0000644000000000000000000000302100000000000016726 0ustar00"""Checks that links are viable. --- layout: post source: SublimeLinter-annotations source_url: http://bit.ly/16Q7H41 title: broken links date: 2014-06-10 12:31:19 categories: writing --- Check that links are not broken. """ import re import urllib.request as urllib_request # for Python 3 from socket import error as SocketError from future import standard_library from proselint.tools import memoize standard_library.install_aliases() @memoize def check(text): """Check the text.""" err = "links.valid" msg = "Broken link: {}" regex = re.compile( r"""(?i)\b((?:https?://|www\d{0,3}[.] |[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+ |(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\) |[^\s`!()\[\]{};:\'".,<>?\xab\xbb\u201c\u201d\u2018\u2019\u21a9]))""", re.U | re.X) errors = [] for m in re.finditer(regex, text): url = m.group(0).strip() if "http://" not in url and "https://" not in url: url = "http://" + url if is_broken_link(url): errors.append((m.start(), m.end(), err, msg.format(url), None)) return errors @memoize def is_broken_link(url): """Determine whether the link returns a 404 error.""" try: request = urllib_request.Request( url, headers={'User-Agent': 'Mozilla/5.0'}) urllib_request.urlopen(request).read() return False except urllib_request.URLError: return True except SocketError: return True ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/malapropisms/__init__.py0000644000000000000000000000002500000000000020575 0ustar00"""Malaproprisms.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/malapropisms/misc.py0000644000000000000000000000107600000000000020000 0ustar00"""Malaproprisms. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: Malaproprisms date: 2014-06-10 12:31:19 categories: writing --- Archaism. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "malapropisms.misc" msg = "'{}' is a malapropism." illogics = [ "the infinitesimal universe", "a serial experience", "attack my voracity", ] return existence_check(text, illogics, err, msg, offset=1) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/__init__.py0000644000000000000000000000006600000000000017026 0ustar00"""Miscellaneous advice not otherwise categorized.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/apologizing.py0000644000000000000000000000102600000000000017606 0ustar00"""Excessive apologizing. --- layout: post source: Pinker's book on writing source_url: ??? title: excessive apologizing date: 2014-06-10 12:31:19 categories: writing --- Points out excessive apologizing. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Suggest the preferred forms.""" err = "pinker.apologizing" msg = "Excessive apologizing." narcissism = [ "More research is needed", ] return existence_check(text, narcissism, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/back_formations.py0000644000000000000000000000106400000000000020427 0ustar00"""Back-formations. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: back-formations date: 2014-06-10 12:31:19 categories: writing --- Back-formations. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "misc.back_formations" msg = "Back-formation. '{}' is the preferred form." list = [ ["improper", ["improprietous"]], ] return preferred_forms_check(text, list, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/bureaucratese.py0000644000000000000000000000106300000000000020117 0ustar00"""Bureaucratese. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: bureaucratese date: 2014-06-10 12:31:19 categories: writing --- Bureaucratese. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "misc.bureaucratese" msg = "'{}' is bureaucratese." bureaucratese = [ "meet with your approval", "meets with your approval", ] return existence_check(text, bureaucratese, err, msg, join=True) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/but.py0000644000000000000000000000100700000000000016055 0ustar00"""Don't start a paragraph with 'But'. --- layout: source: Justin Jungé source_url: title: date: 2016-03-10 12:31:19 categories: writing --- Paragraphs should not start with certain bad words. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Do not start a paragraph with a 'But'.""" err = "misc.but" msg = "No paragraph should start with a 'But'." regex = r"(^|([\n\r]+))(\s*)But" return existence_check(text, [regex], err, msg, ignore_case=False) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/capitalization.py0000644000000000000000000000472500000000000020310 0ustar00"""Incorrect capitalization. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: incorrect captalization date: 2014-06-10 12:31:19 categories: writing --- Incorrect capitalization. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "misc.captalization" msg = "Incorrect capitalization. '{}' is the preferred form." list = [ ["Stone Age", ["stone age"]], ["space age", ["Space Age"]], ["the American West", ["the American west"]], ["Mother Nature", ["mother nature"]], ] return preferred_forms_check(text, list, err, msg, ignore_case=False) # @memoize # def check_seasons(text): # """Suggest the preferred forms.""" # err = "MAU102" # msg = "Seasons shouldn't be capitalized. '{}' is the preferred form." # list = [ # # ["winter", ["Winter"]], # # ["fall", ["Fall"]], # # ["summer", ["Summer"]], # # ["spring", ["Spring"]], # ] # return preferred_forms_check(text, list, err, msg, ignore_case=False) @memoize def check_months(text): """Suggest the preferred forms.""" err = "MAU102" msg = "Months should be capitalized. '{}' is the preferred form." list = [ ["January", ["january"]], ["February", ["february"]], # ["March", ["march"]], ["April", ["april"]], # ["May", ["may"]], ["June", ["june"]], ["July", ["july"]], ["August", ["august"]], ["September", ["september"]], ["October", ["october"]], ["November", ["november"]], ["December", ["december"]], ] return preferred_forms_check(text, list, err, msg, ignore_case=False) @memoize def check_days(text): """Suggest the preferred forms.""" err = "MAU102" msg = "Days of the week should be capitalized. '{}' is the preferred form." list = [ ["Monday", ["monday"]], ["Tuesday", ["tuesday"]], ["Wednesday", ["wednesday"]], ["Thursday", ["thursday"]], ["Friday", ["friday"]], ["Saturday", ["saturday"]], ["Sunday", ["sunday"]], ] return preferred_forms_check(text, list, err, msg, ignore_case=False) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/chatspeak.py0000644000000000000000000000137600000000000017237 0ustar00"""Chatspeak. --- layout: post source: ??? source_url: ??? title: textese date: 2014-06-10 12:31:19 categories: writing --- Chatspeak. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "misc.chatspeak" msg = "'{}' is chatspeak. Write it out." words = [ "2day", "4U", "AFAIK", "AFK", "AFK", "ASAP", "B4", "brb", "btw", "cya", "GR8", "lol", "LOL", "LUV", "OMG", "rofl", "roftl", "sum1", "SWAK", "THNX", "THX", "TTYL", "XOXO" ] return existence_check(text, words, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/commercialese.py0000644000000000000000000000235400000000000020101 0ustar00"""Commercialese. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: commercialese date: 2014-06-10 12:31:19 categories: writing --- Commercialese. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "misc.commercialese" msg = "'{}' is commercialese." commercialese = [ "acknowledging yours of", "beg to advise", "enclosed herewith", "enclosed please find", "further to yours of", "further to your letter", "in regard to", r"inst\.", "in the amount of", "of even date", "pending receipt of", "please be advised that", "please return same", "pleasure of a reply", r"prox\.", "pursuant to your request", "regarding the matter", "regret to inform", "thanking you in advance", "the undersigned", "this acknowledges your letter", r"ult\." "we are pleased to note", "with regard to", "your favor has come to hand", "yours of even date" ] return existence_check(text, commercialese, err, msg, join=True) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/composition.py0000644000000000000000000000663600000000000017643 0ustar00"""Elementary Rules of Usage. --- layout: post source: Strunk & White source_url: ??? title: Elementary Principles of Composition date: 2014-06-10 12:31:19 categories: writing --- Strunk & White say: 1. Choose a suitable design and hold to it. * MDPNB: Sounds like a principle of `consistency`. 2. Make the paragraph the unit of composition. * MDPNB: This can be generalized to say something about variability in the length of paragraphs and sentences. When any device is too often used it becomes a mannerism. * MDPNB: Sounds like a principle of `variation`. 3. Use the active voice. 4. Put statements in positive form. * MDPNB: In some cases this will apply as an invective against the use of a double negative. * Ex: He was not very often on time. -> He usually came late. * Ex: 4.1. Placing negative and positive in opposition makes for a stronger structure. * Ex. Not charity, but simple justice. * Not that I loved Caesar less, but that I loved Rome more. 4.2. Do not use unnecessary auxiliaries or conditionals. 5. Use definite, specific, concrete language. * A period of unfavorable weather set in. ->It rained every day for a week. 6. Omit needless words. * `The fact that` is particularly pernicious. * `who is, which was` and the like are often superfluous 7. Avoid a succession of loose sentences. * MDPNB Principle of brevity. Take 2. 8. Express coordinate ideas in similar form. * MDPNB: Principle of parallel structure. * MDPNB: This one will be hard... 9. Keep related words together. * MDPNB: Principle of localism in semantics. 10. In summaries, keep to one tense. * MDPNB: Principle of temporal consistency. 11. Place the emphatic word of a sentence at the end. * MDPNB: Principle of recency. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "strunk_white.composition" msg = "Try '{}' instead of '{}'." bad_forms = [ # Put statements in positive form ["dishonest", ["not honest"]], ["trifling", ["not important"]], ["forgot", ["did not remember"]], ["ignored", ["did not pay (any )?attention to"]], ["distrusted", ["did not have much confidence in"]], # Omit needless words ["whether", ["the question as to whether"]], ["no doubt", ["there is no doubt but that"]], ["used for fuel", ["used for fuel purposes"]], ["he", ["he is a man who"]], ["hastily", ["in a hasty manner"]], ["this subject", ["this is a subject that"]], ["Her story is strange.", ["Her story is a strange one."]], ["because", ["the reason why is that"]], ["because / since", ["owing to the fact that"]], ["although / though", ["in spite of the fact that"]], ["remind you / notify you", ["call your attention to the fact that"]], ["I did not know that / I was unaware that", ["I was unaware of the fact that"]], ["his failure", ["the fact that he had not succeeded"]], ["my arrival", ["the fact that i had arrived"]] ] return preferred_forms_check(text, bad_forms, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/currency.py0000644000000000000000000000076200000000000017124 0ustar00"""Currency. --- layout: post source: SublimeLinter-annotations source_url: http://bit.ly/16Q7H41 title: symbols date: 2014-06-10 12:31:19 categories: writing --- Symbols. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "misc.currency" msg = "Incorrect use of symbols in {}." symbols = [ r"\$[\d]* ?(?:dollars|usd|us dollars)" ] return existence_check(text, symbols, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/debased.py0000644000000000000000000000115700000000000016660 0ustar00"""Debased language. --- layout: post source: ??? source_url: ??? title: yelling date: 2014-06-10 12:31:19 categories: writing --- Too much yelling. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "misc.debased" msg = "Bad usage, debased language, a continuous temptation." list = [ "a not unjustifiable assumption", "leaves much to be desired", "would serve no purpose", "a consideration which we should do well to bear in mind", ] return existence_check(text, list, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/errata.md0000644000000000000000000000011200000000000016505 0ustar00? Page 82. Inconsistent spelling of "back-formations" as "backformation". ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/false_plurals.py0000644000000000000000000000145500000000000020126 0ustar00"""False plurals. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: False plurals. date: 2014-06-10 12:31:19 categories: writing --- Using the incorrect form of the plural. """ from proselint.tools import existence_check, memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "misc.false_plurals.examples" msg = "The plural is {}" preferences = [ ["talismans", ["talismen"]], ["phenomena", ["phenomenons"]], ] return preferred_forms_check(text, preferences, err, msg) @memoize def check_kudos(text): """Check the text.""" err = "misc.false_plurals.kudos" msg = "Kudos is singular." return existence_check(text, ["many kudos"], err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/greylist.py0000644000000000000000000000167500000000000017140 0ustar00"""Use of greylisted words. --- layout: post source: Strunk & White source_url: ??? title: Use of greylisted words date: 2014-06-10 12:31:19 categories: writing --- Strunk & White say: """ import re from proselint.tools import memoize @memoize def check(text): """Check the text.""" err = "strunk_white.greylist" msg = "Use of '{}'. {}" bad_words = [ "obviously", "utilize" ] explanations = { "obviously": "This is obviously an inadvisable word to use.", "utilize": r"Do you know anyone who *needs* to utilize the word utilize?" } errors = [] for word in bad_words: occ = [m for m in re.finditer(word, text.lower())] for o in occ: errors.append(( o.start(), o.end(), err, msg.format(word, explanations[word]), None)) return errors ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/illogic.py0000644000000000000000000000251100000000000016706 0ustar00"""Illogic. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: Illogic date: 2014-06-10 12:31:19 categories: writing --- Archaism. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "misc.illogic" msg = "'{}' is illogical." illogics = [ "preplan", "more than .{1,10} all", "appraisal valuations?", "(?:i|you|he|she|it|y'all|all y'all|you all|they) could care less", "least worst", "much-needed gaps?", "much-needed voids?", "no longer requires oxygen", "without scarcely", ] return existence_check(text, illogics, err, msg, offset=1) @memoize def check_coin_a_phrase_from(text): """Check the text.""" err = "misc.illogic.coin" msg = "You can't coin an existing phrase. Did you mean 'borrow'?" regex = "to coin a phrase from" return existence_check(text, [regex], err, msg, offset=1) @memoize def check_without_your_collusion(text): """Check the textself.""" err = "misc.illogic.collusion" msg = "It's impossible to defraud yourself. Try 'aquiescence'." regex = "without your collusion" return existence_check( text, [regex], err, msg, require_padding=False, offset=-1) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/inferior_superior.py0000644000000000000000000000127400000000000021036 0ustar00"""Inferior / Superior. --- layout: post source: Fowler's Modern English Usage source_url: bit.ly/1YBG8QJ title: Inferior / Superior date: 2016-03-10 17:27:37 categories: writing --- Corrects 'inferior/superior than' to 'inferior/superior to'. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "misc.inferior_superior" msg = "'Inferior' and 'superior' are not true comparatives. Use '{}'." preferences = [ ["inferior to", ["inferior than"]], ["superior to", ["superior than"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/institution_name.py0000644000000000000000000000137300000000000020662 0ustar00"""Common errors with institution names. --- layout: post source: Institution's webpage source_url: http://bit.ly/2en1zbv, title: Institution Name date: 2016-11-16 11:46:19 categories: writing --- Institution names. """ from proselint.tools import memoize, preferred_forms_check @memoize def check_vtech(text): """Suggest the correct name. source: Virginia Tech Division of Student Affairs source_url: http://bit.ly/2en1zbv """ err = "institution.vtech" msg = "Incorrect name. Use '{}' instead of '{}'." institution = [ ["Virginia Polytechnic Institute and State University", ["Virginia Polytechnic and State University"]], ] return preferred_forms_check(text, institution, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/latin.py0000644000000000000000000000136700000000000016403 0ustar00"""Back-formations. --- layout: post source: The sense of style source_url: http://amzn.to/1EOUZ5g title: back-formations date: 2014-06-10 12:31:19 categories: writing --- Back-formations. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "pinker.latin" msg = "Use English. '{}' is the preferred form." list = [ ["other things being equal", ["ceteris paribus"]], ["among other things", ["inter alia"]], ["in and of itself", ["simpliciter"]], ["having made the necessary changes", ["mutatis mutandis"]], ] return preferred_forms_check(text, list, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/many_a.py0000644000000000000000000000126500000000000016535 0ustar00"""Many a singular. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: Many a singular. date: 2014-06-10 12:31:19 categories: writing --- The idiom 'many a' requires a singular verb. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "misc.many_a" msg = "'many a' requires a singular verb." preferences = [ ["is many a", ["are many a"]], ["has been many a", ["have been many a"]], ["was many a", ["were many a"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/metaconcepts.py0000644000000000000000000000106300000000000017752 0ustar00"""Metaconcepts. --- layout: post source: Pinker's book on writing source_url: ??? title: misuse of scare quotes date: 2014-06-10 12:31:19 categories: writing --- Point out misuse of scare quotes. """ # from proselint.tools import memoize, existence_check # @memoize # def check(text): # """Suggest the preferred forms.""" # err = "pinker.metaconcepts" # msg = "Misuse of 'scare quotes'. Delete them." # narcissism = [ # "the 'take-home message'", # ] # return existence_check(text, narcissism, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/metadiscourse.py0000644000000000000000000000125100000000000020133 0ustar00"""Metadiscourse. --- layout: post source: Pinker's book on writing source_url: ??? title: metadiscourse date: 2014-06-10 12:31:19 categories: writing --- Points out metadiscourse. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Suggest the preferred forms.""" err = "pinker.metadiscourse" msg = "Excessive metadiscourse." metadiscourse = [ "The preceeding discussion", "The rest of this article", "This chapter discusses", "The preceding paragraph demonstrated", "The previous section analyzed", ] return existence_check(text, metadiscourse, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/narcissism.py0000644000000000000000000000114600000000000017442 0ustar00"""Professional narcissism. --- layout: post source: Pinker's book on writing source_url: ??? title: professional narcissism date: 2014-06-10 12:31:19 categories: writing --- Points out academic narcissism. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Suggest the preferred forms.""" err = "pinker.narcissism" msg = "Professional narcissism. Talk about the subject, not its study." narcissism = [ "In recent years, an increasing number of [a-zA-Z]{3,}sts have", ] return existence_check(text, narcissism, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/not_guilty.py0000644000000000000000000000204500000000000017463 0ustar00"""Not guilty beyond a reasonable doubt. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: Not guilty beyond a reasonable doubt. date: 2016-03-09 15:50:31 categories: writing --- This phrasing is ambiguous. The standard by which a jury decides criminal charges is this: a defendant is guilty only if the evidence shows, beyond a reasonable doubt, that he or she committed the crime. Otherwise, the defendant is not guilty. Thus, we say that a defendant was not found "guilty beyond a reasonable doubt." If somebody is found not guilty, say "not guilty." Omit the standard ("beyond a reasonable doubt") to prevent a miscue. Not guilty beyond a reasonable doubt """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "misc.not_guilty" msg = "'not guilty beyond a reasonable doubt' is an ambiguous phrasing." regex = r"not guilty beyond (a |any )?reasonable doubt" return existence_check(text, [regex], err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/phrasal_adjectives.py0000644000000000000000000001544600000000000021132 0ustar00"""Phrasal adjectives. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: Phrasal adjectives date: 2014-06-10 12:31:19 categories: writing --- Phrasal adjectives. """ from proselint.tools import existence_check, memoize, preferred_forms_check @memoize def check_ly(text): """Check the text.""" err = "garner.phrasal_adjectives.ly" msg = """No hyphen is necessary in phrasal adjectives with an adverb ending in -ly, unless the -ly adverb is part of a longer phrase""" regex = r"\s[^\s-]+ly-" return existence_check(text, [regex], err, msg, require_padding=False, offset=-1) @memoize def check(text): """Check the text.""" err = "garner.phrasal_adjectives.examples" msg = """Hyphenate '{1}', a phrasal adjective, as '{0}'.""" list = [ ["across-the-board discounts", ["across the board discounts"]], ["acute-care treatment", ["acute care treatment"]], ["agreed-upon answer", ["agreed upon answer"]], ["big-ticket item", ["big ticket item"]], ["class-action lawyer", ["class action lawyer"]], ["criminal-law professor", ["criminal law professor"]], ["cut-and-dried issue", ["cut and dried issue"]], ["downward-sloping line", ["downward sloping line"]], ["English-language learners", ["English language learners"]], ["English-speaking people", ["English speaking people"]], ["even-numbered", ["even numbered"]], ["face-to-face meeting", ["fact to face meeting"]], ["fixed-rate mortgage", ["fixed rate mortgage"]], ["for-profit firm", ["for profit firm"]], ["foreign-sounding name", ["foreign sounding name"]], ["government-owned business", ["government owned business"]], ["hard-and-fast issue", ["hard and fast issue"]], ["head-on collision", ["head on collision"]], ["head-to-head battle", ["head to head battle"]], ["head-to-head competition", ["head to head competition"]], ["health-care coverage", ["heath care coverage"]], ["high-school student", ["high school student"]], ["hit-and-run statute", ["hit and run statute"]], ["HIV-negative person", ["HIV negative person"]], ["HIV-positive person", ["HIV positive person"]], ["information-technology personnel", ["information technology personnel"]], ["intellectual-property rights", ["intellectual property rights"]], ["interest-group pressures", ["interest group pressures"]], ["joint-stock company", ["joint stock company"]], ["kidney-dialysis machine", ["kidney dialysis machine"]], ["long-run costs", ["long run costs"]], ["long-term care", ["long term care"]], ["low-income housing", ["low income housing"]], ["mom-and-pop retail outlet", ["mom and pop retail outlet"]], ["mom-and-pop shop", ["mom and pop shop"]], ["national-security briefing", ["national security briefing"]], ["natural-gas pipeline", ["natural gas pipeline"]], ["no-fault accident", ["no fault accident"]], ["no-fault divorce", ["no fault divorce"]], ["no-fault insurance", ["no fault insurance"]], ["odd-numbered", ["odd numbered"]], ["office-supply store", ["office supply store"]], ["one-way flight", ["one way flight"]], ["one-way window", ["one way window"]], ["open-and-shut case", ["open and shut case"]], ["open-and-shut issue", ["open and shut issue"]], ["open-source community", ["open source community"]], ["optical-scan ballot", ["optical scan ballot"]], ["pension-fund investments", ["pension fund investments"]], ["private-sector employment", ["private sector employment"]], ["profit-seeking efforts", ["profit seeking efforts"]], ["punch-card ballot", ["punch card ballot"]], ["quality-adjusted price", ["quality adjusted price"]], ["razor-sharp intellect", ["razor sharp intellect"]], ["razor-sharp mind", ["razor sharp mind"]], ["razor-sharp mind", ["razor sharp mind"]], ["razor-sharp wit", ["razor sharp wit"]], ["larger-than-life personality", ["larger than life personality"]], ["real-estate prices", ["real estate prices"]], ["real-estate tycoon", ["real estate tycoon"]], ["right-wing militia", ["right wing militia"]], ["round-trip flight", ["round trip flight"]], ["search-and-rescue operation", ["search and rescue operation"]], ["second-largest army", ["second largest army"]], ["shell-shocked mothers", ["shell shocked mothers"]], ["shell-shocked soldiers", ["shell shocked soldiers"]], ["small-business loan", ["small business loan"]], ["small-business owner", ["small business owner"]], ["small-state senator", ["small state senator"]], ["small-animal veterinarian", ["small animal veterinarian"]], ["state-sponsored terrorism", ["state sponsored terrorism"]], ["state-sponsored violence", ["state sponsored violence"]], ["thumbs-up sign", ["thumbs up sign"]], ["time-honored tradition", ["time honored tradition"]], ["U.S.-led campaign", ["U.S. led campaign"]], ["upward-sloping line", ["upward sloping line"]], ["venture-backed company", ["venture backed company"]], ["well-publicized event", ["well publicized event"]], ["wire-transfer service", ["wire transfer service"]], ["yes-or-no question", ["yes or no question"]], ["zero-sum game", ["zero sum game"]], ["stained-glass ceiling", ["stained glass ceiling"]], ["stained-glass window", ["stained glass window"]], ["free-range chicken", ["free range chicken"]], ["free-range poultry", ["free range poultry"]], ["non-profit-making organization", ["non profit making organization", "non-profit making organization", "non profit-making organization"]], # Harmony ["three-part harmony", ["three part harmony"]], ["four-part harmony", ["four part harmony"]], ["six-part harmony", ["six part harmony"]], ["eight-part harmony", ["eight part harmony"]], # Losses and gains. ["first-quarter loss", ["first quarter loss"]], ["second-quarter loss", ["second quarter loss"]], ["third-quarter loss", ["third quarter loss"]], ["fourth-quarter loss", ["fourth quarter loss"]], ["first-quarter gain", ["first quarter gain"]], ["second-quarter gain", ["second quarter gain"]], ["third-quarter gain", ["third quarter gain"]], ["fourth-quarter gain", ["fourth quarter gain"]], ] return preferred_forms_check(text, list, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/preferred_forms.py0000644000000000000000000001661300000000000020460 0ustar00"""Preferred forms. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: preferred forms date: 2014-06-10 12:31:19 categories: writing --- Points out preferred forms. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "garner.preferred_forms" msg = "'{}' is the preferred form." preferences = [ # Obsolete words ["imprimatur", ["imprimature"]], # Proper nouns ["Halloween", ["haloween", "hallowe'en"]], ["Khrushchev", ["Khruschev", "Kruschev"]], ["Ku Klux Klan", ["Klu Klux Klan"]], ["Pontius Pilate", ["Pontius Pilot"]], # Plurals ["hippopotamuses", ["hippopotami"]], ["manifestos", ["manifesti"]], # ["matrixes", ["matrices"]], ["mongooses", ["mongeese"]], ["narcissi", ["narcissuses"]], ["retinas", ["retinae"]], ["sopranos", ["soprani"]], ["titmice", ["titmouses"]], # Hyphenated words ["long-standing", ["longstanding"]], ["sans serif", ["sans-serif", "sanserif"]], ["tortfeasor", ["tort feasor", "tort-feasor"]], ["transship", ["tranship", "trans-ship"]], ["transshipped", ["transhipped", "trans-shipped"]], ["transshipping", ["transhipping", "trans-shipping"]], ["non sequitur", ["non-sequitur"]], # Misc ["attitude", ["mental attitude"]], ["Chief Justice of the United States", ["Chief Justice of the United States Supreme Court", "Chief Justice of the Supreme Court of the United States."]], ["chitterlings", ["chitlings", "chitlins"]], ["combustion engine", ["combustible engine"]], ["during / throughout", ["for the duration of"]], ["foreclose on", ["foreclose againt"]], ["friend in common", ["mutual friend"]], ["in regard to", ["in regards to"]], ["infectious", ["infectuous"]], ["inferable", ["inferrable", "inferrible"]], ["knowing that", ["in light of the fact that"]], ["lanyard", ["laniard"]], ["largess", ["largesse"]], ["lasagna", ["lasagne"]], ["leery", ["leary"]], ["lend me her", ["loan me her"]], ["lend me his", ["loan me his"]], ["lend me their", ["loan me their"]], ["lend me your", ["loan me your"]], ["lent me her", ["loaned me her"]], ["lent me his", ["loaned me his"]], ["lent me their", ["loaned me their"]], ["lent me your", ["loaned me your"]], ["linguist", ["linguistician"]], ["matzo-ball", ["matzoh-ball", "matza-ball", "matzah-ball", "matsah-ball"]], ["mayoralty", ["mayorality"]], ["mealy-mouthed", ["mealymouthed"]], ["mean-spirited", ["meanspirited"]], ["midwifed", ["midwived"]], ["moniker", ["monicker"]], ["musical revue", ["musical review"]], ["mustache", ["moustache"]], ["nonplussed", ["nonplused"]], ["nonplussing", ["nonplusing"]], ["non sequitur", ["nonsequitur"]], ["not nearly as", ["nowhere near as"]], ["off", ["off of"]], ["podiatrist", ["chiropodist"]], ["podiatry", ["chiropody"]], ["shoo-in", ["shoe-in"]], ["suicide", ["suicide victim"]], ["the highway median", ["the highway medium"]], ["vaipidity", ["vapidness"]], ["weather vane", ["weather vein", "weather vain"]], ["with regard to", ["with regards to"]], # Idioms ["a couple of people", ["a couple people"]], ["all the time", ["all of the time"]], ["as follows", ["as follow"]], ["bulk large", ["bulk largely"]], ["burying the lede", ["burying the lead"]], ["came to naught", ["came to nought"]], ["come off it", ["come off of it"]], ["corroborating evidence", ["corroborative evidence"]], ["dear departed", ["dearly departed"]], ["default on a loan", ["default to a loan"]], ["draw an inference", ["make an inference"]], ["in the meantime", ["in the meanwhile"]], ["long distances", ["lengthy distances"]], ["madding crowd", ["maddening crowd"]], ["Magna Carta", ["Magna Charta"]], ["marriage of convenience", ["mariage de convenance"]], ["Meanwhile,", ["Meantime,"]], ["Midwest", ["Middle West"]], ["Midwestern", ["Middle Western"]], ["modi operandi", ["modes of operandi"]], ["modus operandi", ["mode of operandi"]], ["motion seconded", ["notion seconded"]], ["mucous membranes", ["mucus membranes"]], ["must pass muster", ["must past muster"]], ["neck-and-neck", ["neck-in-neck"]], ["no-holds-barred", ["no-holes-barred"]], ["oil magnate", ["oil magnet"]], ["punch up the lede", ["punch up the lead"]], ["railroad magnate", ["railroad magnet"]], ["seconded the motion", ["seconded the notion"]], ["statute of limitationas", ["statute of limits"]], ["take precedence over", ["take prescience over"]], ["the last two", ["both of the last two"]], ["the last two", ["both of the last"]], ["unorganic food", ["inorganic food"]], ["vale of tears", ["veil of tears"]], ["Venus flytrap", ["Venus's flytrap", "Venus' flytrap"]], ["was accused of", ["was accused with"]], # Verbosity ["try to", ["make an attempt to"]], ["try to", ["make attempts to"]], ["try to", ["make efforts to"]], ["tried to", ["made an attempt to"]], ["tried to", ["made attempts to"]], ["tried to", ["made efforts to"]], ["modern", ["modern-day"]], # Grammar ["be misled", ["be mislead"]], ["was misled", ["was mislead"]], ["were misled", ["were mislead"]], # Euphemisms ["a search-and-destroy mission", ["armed reconnaissance"]], ["abortion", ["pregnancy termination"]], ["bisexual", ["sexually ambidextrous"]], ["exterminator", ["extermination engineer"]], ["firing", ["permanent layoff"]], ["rat-catcher", ["rodent operative"]], # Tenses ["mistook", ["mistaked"]], # Accents ["né", ["ne"]], ["née", ["nee"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/pretension.py0000644000000000000000000000114400000000000017453 0ustar00"""Pretension. --- layout: post source: ??? source_url: ??? title: yelling date: 2014-06-10 12:31:19 categories: writing --- Never use the phrase 'all hell broke loose'. """ from proselint.tools import existence_check, max_errors, memoize @max_errors(1) @memoize def check(text): """Check the text.""" err = "ogilvy.pretension" msg = "Jargon words like this one are the hallmarks of a pretentious ass." list = [ "reconceptualize", "demassification", "attitudinally", "judgmentally", ] return existence_check(text, list, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/professions.py0000644000000000000000000000102100000000000017631 0ustar00"""Profession. --- layout: post source: source_url: title: Professions date: 2014-06-10 12:31:19 categories: writing --- Professions. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "misc.professions" msg = "'{}' is the name of that job." preferences = [ ["cobbler", ["shoe repair guy"]], ["geometer", ["geometrist"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/punctuation.py0000644000000000000000000000077100000000000017643 0ustar00"""Punctuation. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: dates date: 2014-06-10 12:31:19 categories: writing --- Dates. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "garner.punctuation" msg = "Misplaced punctuation. It's 'et al.'" list = [ "et. al", "et. al." ] return existence_check(text, list, err, msg, join=True) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/scare_quotes.py0000644000000000000000000000105200000000000017760 0ustar00"""Misuse of scare quotes. --- layout: post source: Pinker's book on writing source_url: ??? title: misuse of scare quotes date: 2014-06-10 12:31:19 categories: writing --- Points out misuse of scare quotes. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Suggest the preferred forms.""" err = "pinker.scare_quotes" msg = "Misuse of 'scare quotes'. Delete them." narcissism = [ "the 'take-home message'", ] return existence_check(text, narcissism, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/suddenly.py0000644000000000000000000000271200000000000017116 0ustar00"""Suddenly. --- layout: post source: Reference for Writers source_url: http://bit.ly/1E94vyD title: suddenly date: 2014-06-10 12:31:19 categories: writing --- “Sudden” means quickly and without warning, but using the word “suddenly” both slows down the action and warns your reader. Do you know what’s more effective for creating the sense of the sudden? Just saying what happens. When using “suddenly,” you communicate through the narrator that the action seemed sudden. By jumping directly into the action, you allow the reader to experience that suddenness first hand. “Suddenly” also suffers from being nondescript, failing to communicate the nature of the action itself; providing no sensory experience or concrete fact to hold on to. Just … suddenly. Feel free to employ “suddenly” in situations where the suddenness is not apparent in the action itself. For example, in “Suddenly, I don’t hate you anymore,” the “suddenly” substantially changes the way we think about the shift in emotional calibration. """ from proselint.tools import existence_check, max_errors, memoize @max_errors(3) @memoize def check(text): """Advice on sudden vs suddenly.""" err = "misc.suddenly" msg = "Suddenly is nondescript, slows the action, and warns your reader." regex = "Suddenly," return existence_check(text, [regex], err, msg, require_padding=False, offset=-1, ignore_case=False) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/tense_present.py0000644000000000000000000000204300000000000020142 0ustar00"""Tense present. --- layout: post source: DFW's Tense Present source_url: http://bit.ly/1c85lgR title: Tense present date: 2014-06-10 12:31:19 categories: writing --- Archaism. """ import re from proselint.tools import memoize @memoize def check(text): """Check the text.""" err = "misc.tense_present" msg = r"'{}'." illogics = [ r"up to \d{1,3}% ?[-\u2014\u2013]{0,3} ?(?:or|and) more\W?", "between you and I", "on accident", "somewhat of a", "all it's own", "reason is because", "audible to the ear", "in regards to", "would of", # "and so", r"i ?(?:feel|am feeling|am|'m|'m feeling) nauseous", ] errors = [] for i in illogics: for m in re.finditer(fr"\s{i}\s", text, flags=re.U | re.I): txt = m.group(0).strip() errors.append(( m.start() + 1, m.end(), err, msg.format(txt), None)) return errors ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/usage.py0000644000000000000000000000374300000000000016400 0ustar00"""Elementary Rules of Usage. --- layout: post source: Strunk & White source_url: ??? title: Elementary Rules of Usage date: 2014-06-10 12:31:19 categories: writing --- Strunk & White say: 1. Form the possessive singular of nouns by adding 's. 2. In a series of three or more terms with a conjunction, use a comma after each term except the last. 3. Enclose parenthetic expressions between commas. ("This rule is difficult to apply.") 4. Place a comma before a conjunction introducing an independent clause. 5. Do not join independent clauses with a comma; use a semi-colon. Or a period. 6. Do not break sentences in two. Do not use periods for the role of commas. 7. Use a colon after an independent clause if you introduce: a list of particulars, an appositive, an application, or an illustrative quotation. 8. Use a dash to set off an abrupt break or interruption and to announce a long appositive or summary. 9. The number of the subject determines the number of the verb. (MDPNB: This will require nltk & syntactic parsing) 10. Use the proper case(grammatical gender) of pronouns. * MDPNB: hard case: "Give this work to whoever looks idle." `whoever looks idle` is the object of `to`. 11. A participial phrase at the beginning of a sentence must refer to the grammatical subject. """ # from proselint.tools import memoize # import re # @memoize # def check(text): # err = "strunk_white.usage" # msg = "Use of '{}'. {}" # bad_words = [ # "obviously", # "utilize" # ] # explanations = { # "obviously": # "This is obviously an inadvisable word to use.", # "utilize": # r"Do you know anyone who *needs* to utilize the word utilize?" # } # errors = [] # for word in bad_words: # occ = [m for m in re.finditer(word, text.lower())] # for o in occ: # errors.append((o.start(), o.end(), err, # msg.format(word, explanations[word]))) # return errors ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/waxed.py0000644000000000000000000000346200000000000016402 0ustar00"""Waxed lyrical. --- layout: post source: Fowler's Modern English Usage source_url: bit.ly/1YBG8QJ title: Waxed lyrical date: 2016-03-10 14:48:42 categories: writing --- Fowler's says: Its primary meaning 'grow larger, increase' (as opposed to 'wane') leads naturally to the sense 'pass into a specified state or mood, begin to use a specified tone. In this meaning a following modifier must be an adj. not an adverb ('He waxed enthusiastic [not enthusiastically] about Australia'). """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "misc.waxed" msg = "The modifier following 'waxed' must be an adj.: '{}' is correct" waxes = ["wax", "waxes", "waxed", "waxing"] modifiers = [("ebullient", "ebulliently"), ("ecstatic", "ecstatically"), ("eloquent", "eloquently"), ("enthusiastic", "enthusiastically"), ("euphoric", "euphorically"), ("indignant", "indignantly"), ("lyrical", "lyrically"), ("melancholic", "melancholically"), ("metaphorical", "metaphorically"), ("nostalgic", "nostalgically"), ("patriotic", "patriotically"), ("philosophical", "philosophically"), ("poetic", "poetically"), ("rhapsodic", "rhapsodically"), ("romantic", "romantically"), ("sentimental", "sentimentally") ] def pairs(word): return [[word + ' ' + pair[0], [word + ' ' + pair[1]]] for pair in modifiers] preferred = [] for word in waxes: preferred += pairs(word) return preferred_forms_check(text, preferred, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/misc/whence.py0000644000000000000000000000066300000000000016543 0ustar00"""From whence it came. --- layout: post source: unknown source_url: unknown title: whence date: 2014-06-10 12:31:19 categories: writing --- From whence it came. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "misc.whence" msg = "The 'from' in 'from whence' is not needed." return existence_check(text, ["from whence"], err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/mixed_metaphors/__init__.py0000644000000000000000000000002700000000000021260 0ustar00"""Mixed metaphors.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/mixed_metaphors/misc.py0000644000000000000000000000257200000000000020463 0ustar00"""Mixed metaphors.""" from proselint.tools import (existence_check, max_errors, memoize, preferred_forms_check) @max_errors(1) @memoize def check_bottleneck(text): """Avoid mixing metaphors about bottles and their necks. source: Sir Ernest Gowers source_url: http://bit.ly/1CQPH61 """ err = "mixed_metaphors.misc.bottleneck" msg = "Mixed metaphor — bottles with big necks are easy to pass through." list = [ "biggest bottleneck", "big bottleneck", "large bottleneck", "largest bottleneck", "world-wide bottleneck", "huge bottleneck", "massive bottleneck", ] return existence_check(text, list, err, msg) @memoize def check_misc(text): """Avoid mixing metaphors. source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY """ err = "mixed_metaphors.misc.misc" msg = "Mixed metaphor. Try '{}'." preferences = [ ["cream rises to the top", ["cream rises to the crop"]], ["fasten your seatbelts", ["button your seatbelts"]], ["a minute to decompress", ["a minute to decompose"]], ["sharpest tool in the shed", ["sharpest marble in the (shed|box)"]], ["not rocket science", ["not rocket surgery"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/mondegreens/__init__.py0000644000000000000000000000002300000000000020372 0ustar00"""Mondegreens.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/mondegreens/misc.py0000644000000000000000000000205200000000000017572 0ustar00"""Mondegreens. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: mondegreens date: 2014-06-10 12:31:19 categories: writing --- Points out preferred form. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "misc.mondegreens" msg = "'{}' is the preferred form." list = [ ["a girl with kaleidoscope eyes", ["a girl with colitis goes by"]], ["a partridge in a pear tree", ["a part-red gingerbread tree"]], ["attorney and notary public", ["attorney and not a republic"]], ["beck and call", ["beckon call"]], ["for all intents and purposes", ["for all intensive purposes"]], ["laid him on the green", ["Lady Mondegreen"]], ["all of the other reindeer", ["Olive, the other reindeer"]], ["to the manner born", ["to the manor born"]], ] return preferred_forms_check(text, list, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/needless_variants/__init__.py0000644000000000000000000000003100000000000021574 0ustar00"""Needless variants.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/needless_variants/misc.py0000644000000000000000000004222600000000000021004 0ustar00"""Needless variants. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: needless variants date: 2014-06-10 12:31:19 categories: writing --- Points out use of needless variants. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "needless_variants.misc" msg = "Needless variant. '{}' is the preferred form." preferences = [ # Needless variants ["abolition", ["abolishment"]], ["accessory", ["accessary"]], ["accredit", ["accreditate"]], ["accrual", ["accruement"]], ["accumulate", ["cumulate"]], ["accused", ["accusee"]], ["acquaintance", ["acquaintanceship"]], ["acquittal", ["acquitment"]], ["administer", ["administrate"]], ["administered", ["administrated"]], ["administering", ["administrating"]], ["adulterous", ["adulterate"]], ["advisory", ["advisatory"]], ["advocate", ["advocator"]], ["alleger", ["allegator"]], ["allusive", ["allusory"]], ["ameliorate", ["meliorate"]], ["amorous", ["amative"]], ["amortization", ["amortizement"]], ["amphibology", ["amphiboly"]], ["anachronism", ["parachronism"]], ["anecdotist", ["anecdotalist"]], ["anilingus", ["anilinctus"]], ["anticipatory", ["anticipative"]], ["antithetical", ["antithetic"]], ["applicable", ["applicative"]], ["applicable", ["applicatory"]], ["applicator", ["applier"]], ["approbatory", ["approbative"]], ["arbitrageur", ["arbitrager"]], ["arsenious", ["arsenous"]], ["ascendancy", ["ascendance"]], ["ascendancy", ["ascendence"]], ["ascendancy", ["ascendency"]], ["authorial", ["auctorial"]], ["averment", ["averral"]], ["barbed wire", ["barbwire"]], ["beneficent", ["benefic"]], ["benign", ["benignant"]], ["bestowal", ["bestowment"]], ["betrothal", ["betrothment"]], ["blameworthiness", ["blamableness"]], ["buck naked", ["butt naked"]], ["captor", ["capturer"]], ["carte blanche", ["carta blanca"]], ["casualties", ["casualities"]], ["casualty", ["casuality"]], ["catch fire", ["catch on fire"]], ["catholically", ["catholicly"]], ["ceasefire", ["cease fire"]], ["cellphone", ["cell phone", "cell-phone"]], ["channel", ["channelize"]], ["chaplaincy", ["chaplainship"]], ["chrysalis", ["chrysalid"]], ["chrysalises", ["chrysalids"]], ["cigarette", ["cigaret"]], ["cliquish", ["cliquey", "cliquy"]], ["cognitive", ["cognitional"]], ["cohabit", ["cohabitate"]], ["cohabitant", ["cohabitor"]], ["collodion", ["collodium"]], ["collusive", ["collusory"]], ["commemorative", ["commemoratory"]], ["commonage", ["commonty"]], ["communicative", ["communicatory"]], ["compensatory", ["compensative"]], ["complacency", ["complacence"]], ["complicit", ["complicitous"]], ["compute", ["computate"]], ["comrade", ["camarade"]], ["conciliatory", ["conciliative"]], ["concomitance", ["concomitancy"]], ["condonation", ["condonance"]], ["confirmatory", ["confirmative"]], ["congruence", ["congruency"]], ["connote", ["connotate"]], ["consanguine", ["consanguineal"]], ["conspicuousness", ["conspicuity"]], ["conspirator", ["conspiratorialist"]], ["constitutionalist", ["constitutionist"]], ["contemporaneous", ["cotemporaneous"]], ["contemporary", ["cotemporary"]], ["contigency", ["contingence"]], ["contributory", ["contributary"]], ["contumacy", ["contumacity"]], ["convertible", ["conversible"]], ["conveyance", ["conveyal"]], ["corroborative", ["corroboratory"]], ["coworker", ["coemployee"]], ["curative", ["curatory"]], ["daredevilry", ["daredeviltry"]], ["deceptive", ["deceptious"]], ["defamatory", ["defamative"]], ["degenerative", ["degeneratory"]], ["delimit", ["delimitate"]], ["delusive", ["delusory"]], ["denunciation", ["denouncement"]], ["depositary", ["depositee"]], ["depreciatory", ["depreciative"]], ["deprivation", ["deprival"]], ["derogatory", ["derogative"]], ["destructible", ["destroyable"]], ["dethrone", ["disenthrone"]], ["detoxify", ["detoxicate"]], ["detractive", ["detractory"]], ["deuterogamy", ["digamy"]], ["deviance", ["deviancy"]], ["deviant", ["deviationist"]], ["digitize", ["digitalize"]], ["diminution", ["diminishment"]], ["diplomat", ["diplomatist"]], ["disciplinary", ["disciplinatory"]], ["discriminating", ["discriminant"]], ["disintegrative", ["disintegratory"]], ["dismissal", ["dismission"]], ["disorient", ["disorientate"]], ["disoriented", ["disorientated"]], ["disquiet", ["disquieten"]], ["dissociate", ["disassociate"]], ["distrait", ["distraite"]], ["divergence", ["divergency"]], ["divisible", ["dividable"]], ["doctrinaire", ["doctrinary"]], ["documentary", ["documental"]], ["domesticate", ["domesticize"]], ["doubt", ["misdoubt"]], ["duplicative", ["duplicatory"]], ["dutiful", ["duteous"]], ["educationist", ["educationalist"]], ["educative", ["educatory"]], ["empanel", ["impanel"]], ["encumbrance", ["cumbrance"]], ["endow", ["indow"]], ["endue", ["indue"]], ["enigmas", ["enigmatas"]], ["enlarge", ["enlargen"]], ["epic", ["epical"]], ["eroticism", ["erotism"]], ["ethicist", ["ethician"]], ["ex officio", ["ex officiis"]], ["exculpatory", ["exculpative"]], ["exigency", ["exigence"]], ["exigent", ["exigeant"]], ["exoticism", ["exotism"]], ["expediency", ["expedience"]], ["expedient", ["expediential"]], ["expedient", ["expediential"]], ["extendable", ["extensible"]], ["eyeing", ["eying"]], ["fief", ["fiefdom"]], ["flagrancy", ["flagrance"]], ["flatulence", ["flatulency"]], ["fraudulent", ["defraudulent"]], ["fraudulent", ["fraudful"]], ["funereal", ["funebrial"]], ["geographic", ["geographical"]], ["geometric", ["geometrical"]], ["goatherd", ["goatherder"]], ["grievance", ["aggrievance"]], ["gustatory", ["gustatorial"]], ["habit", ["habitude"]], ["henceforth", ["henceforward"]], ["hesitancy", ["hesitance"]], ["heterogeneous", ["heterogenous"]], ["hierarchical", ["hierarchic"]], ["hindmost", ["hindermost"]], ["honoree", ["honorand"]], ["hypostatize", ["hypostasize"]], ["hysterical", ["hysteric"]], ["idolize", ["idolatrize"]], ["impersonation", ["personation"]], ["impervious", ["imperviable"]], ["importunity", ["importunacy"]], ["impotence", ["impotency"]], ["imprimatur", ["imprimatura"]], ["improper", ["improprietous"]], ["incitement", ["incitation"]], ["inconsistency", ["inconsistence"]], ["incriminate", ["criminate"]], ["inculpatory", ["culpatory"]], ["incurrence", ["incurment"]], ["infrequent", ["unfrequent"]], ["inhibitory", ["inhibitive"]], ["innovative", ["innovational"]], ["inquisitorial", ["inquisitional"]], ["insistence", ["insistment"]], ["instillation", ["instillment"]], ["instinctive", ["instinctual"]], ["insubstantial", ["unsubstantial"]], ["insurer", ["insuror"]], ["insurrectionary", ["insurrectional"]], ["interpret", ["interpretate"]], ["intervention", ["intervenience"]], ["ironic", ["ironical"]], ["irrevocable", ["unrevokable"]], ["judgmental", ["judgmatic"]], ["jury-rigged", ["gerry-rigged"]], ["jury-rigged", ["jerry-rigged"]], ["kaffeeklatsch", ["Coffee klatsch", "coffee klatch"]], ["knickknack", ["nicknack"]], ["labyrinthine", ["labyrinthian"]], ["laudatory", ["laudative"]], ["legitimation", ["legitimatization"]], ["legitimation", ["legitimization"]], ["legitimize", ["legitimatize"]], ["lengthwise", ["lengthways"]], ["licorice", ["liquorice"]], ["life-size", ["life-sized"]], ["lithe", ["lithesome"]], ["loath", ["loth"]], ["lollypop", ["lollipop"]], ["lubricious", ["lubricous"]], ["mayhem", ["maihem"]], ["medical marijuana", ["medicinal marijuana"]], ["minimize", ["minimalize"]], ["monetize", ["monetarize"]], ["movable", ["moveable"]], ["murk", ["mirk"]], ["murky", ["mirky"]], ["narcissism", ["narcism"]], ["neglectful", ["neglective"]], ["negligence", ["negligency"]], ["neologist", ["neologizer"]], ["neurological", ["neurologic"]], ["nictitate", ["nictate"]], ["normality", ["normalcy"]], ["numbness", ["numbedness"]], ["omissible", ["omittable"]], ["onomatopoeic", ["onomatopoetic"]], ["opined", ["opinioned"]], ["optimal advantage", ["optimum advantage"]], ["orient", ["orientate"]], ["outsize", ["outsized"]], ["oversize", ["oversized"]], ["overthrow", ["overthrowal"]], ["pacifist", ["pacificist"]], ["parti-colored", ["parti-color"]], ["parti-colored", ["party-colored"]], ["participatory", ["participative"]], ["partner", ["copartner"]], ["partnership", ["copartnership"]], # ["password", ["passcode"]], # FIXME ["patina", ["patine"]], ["pederast", ["paederast"]], ["pediatrician", ["pediatrist"]], ["pejorative", ["perjorative"]], ["penumbral", ["penumbrous"]], ["permissive", ["permissory"]], ["permute", ["permutate"]], ["pharmaceutical", ["pharmaceutic"]], ["pleurisy", ["pleuritis"]], ["policyholder", ["policy holder"]], ["policyholder", ["policyowner"]], ["politicize", ["politicalize"]], ["pre-Columbian", ["precolumbian"]], ["precedence", ["precedency"]], ["preceptorial", ["preceptoral"]], ["precipitancy", ["precipitance"]], ["precipitate", ["precipitant"]], ["preclusive", ["preclusory"]], ["prefectorial", ["prefectoral"]], ["preponderantly", ["preponderately"]], ["preservation", ["preserval"]], ["preventive", ["preventative"]], ["proconsulate", ["proconsulship"]], ["procreative", ["procreational"]], ["procurement", ["procurance"]], ["propulsion", ["propelment"]], ["propulsive", ["propulsory"]], ["prosecutory", ["prosecutive"]], ["protective", ["protectory"]], ["provocative", ["provocatory"]], ["prurience", ["pruriency"]], ["psychical", ["psychal"]], ["punitive", ["punitory"]], ["pygmy", ["pygmean", "pygmaen"]], ["quantify", ["quantitate"]], ["questionnaire", ["questionary"]], ["quiescence", ["quiescency"]], ["rabbi", ["rabbin"]], ["reasonableness", ["reasonability"]], ["recidivous", ["recidivistic"]], ["recriminatory", ["recriminative"]], ["recruitment", ["recruital"]], ["recurrence", ["recurrency"]], ["recusal", ["recusation"]], ["recusal", ["recusement"]], ["recusancy", ["recusance"]], ["redemptive", ["redemptory"]], ["referable", ["referrable"]], ["referable", ["referrible"]], ["refutative", ["refutatory"]], ["remission", ["remittal"]], ["remittance", ["remitment"]], ["renounceable", ["renunciable"]], ["renunciation", ["renouncement"]], ["reparative", ["reparatory"]], ["repudiatory", ["repudiative"]], ["requital", ["requitement"]], ["rescission", ["rescindment"]], ["restoration", ["restoral"]], ["reticence", ["reticency"]], ["retributive", ["retributional", "retributionary"]], ["review", ["reviewal"]], ["revision", ["revisal"]], ["revisionary", ["revisional"]], ["revocable", ["revokable", "revokeable"]], ["revolt", ["revolute"]], ["salience", ["saliency"]], ["salutary", ["salutiferous"]], ["sensory", ["sensatory"]], ["sessional", ["sessionary"]], ["shareholder", ["shareowner"]], ["sickly", ["sicklily"]], ["signatory", ["signator"]], ["slander", ["slanderize"]], ["societal", ["societary"]], ["sodomite", ["sodomist"]], ["solicit", ["solicitate"]], ["speculative", ["speculatory"]], ["spirituous", ["spiritous"]], ["statutory", ["statutorial"]], ["submersible", ["submergeable"]], ["submission", ["submittal"]], ["subtle", ["subtile"]], ["succubus", ["succuba"]], ["sufficiency", ["sufficience"]], ["supplicant", ["suppliant"]], ["surmise", ["surmisal"]], ["suspendable", ["suspendible"]], ["swathe", ["enswathe"]], ["synthesize", ["synthetize"]], ["systematize", ["systemize"]], ["T-shirt", ["tee-shirt"]], ["tactile", ["tactual"]], ["tangential", ["tangental"]], ["tautological", ["tautologous"]], ["thenceforth", ["thenceforward"]], ["transience", ["transiency"]], ["transposition", ["transposal"]], ["transposition", ["transposal"]], ["unalterable", ["inalterable"]], ["uncommunicative", ["incommunicative"]], ["uncontrollable", ["incontrollable"]], ["unenforceable", ["nonenforceable"]], ["unnavigable", ["innavigable"]], ["unreasonableness", ["unreasonability"]], ["unsolvable", ["insolvable"]], ["usurpation", ["usurpature"]], ["variational", ["variative"]], ["vegetative", ["vegetive"]], ["vindictive", ["vindicative"]], ["vituperative", ["vituperous"]], ["vociferous", ["vociferant"]], ["volitional", ["volitive"]], ["wolfish", ["wolvish"]], ["wolverine", ["wolverene"]], ["Zoroastrianism", ["Zoroastrism"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/nonwords/__init__.py0000644000000000000000000000002000000000000017732 0ustar00"""Nonwords.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/nonwords/misc.py0000644000000000000000000000456000000000000017143 0ustar00"""Nonwords. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: nonwords date: 2014-06-10 12:31:19 categories: writing --- Nonwords. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "nonwords.misc" msg = "Nonword, try '{}'." preferences = [ ["doubtless' or 'undoubtedly", ["doubtlessly"]], ["analysis", ["analyzation"]], ["annoyance", ["annoyment"]], ["confirmand", ["confirmant"]], ["confirmands", ["confirmants"]], ["converse", ["conversate"]], ["cranded", ["crained"]], ["disbursement' or 'dispersal", ["dispersement"]], ["discomfort' or 'discomfiture", ["discomforture"]], ["effrontery", ["affrontery"]], ["forbearance", ["forebearance"]], ["improper", ["improprietous"]], ["inclement", ["inclimate"]], ["relatively low price' or 'affordability", ["relative inexpense"]], ["inimical", ["inimicable"]], ["regardless", ["irregardless"]], ["minimize", ["minimalize"]], ["minimized", ["minimalized"]], ["minimizes", ["minimalizes"]], ["minimizing", ["minimalizing"]], # muchly ["optimize", ["optimalize"]], ["paralysis", ["paralyzation"]], ["pettifog", ["pettifogger"]], ["proper", ["proprietous"]], ["quell' or 'quash", ["squelch"]], ["seldom", ["seldomly"]], # slinged ["thus", ["thusly"]], ["categorically", ["uncategorically"]], ["undoubtedly' or 'indubitably", ["undoubtably"]], ["unequivocal", ["unequivocable"]], ["mercilessly", ["unmercilessly"]], ["unrelentingly' or relentlessly", ["unrelentlessly"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/oxymorons/__init__.py0000644000000000000000000000002100000000000020137 0ustar00"""Oxymorons.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/oxymorons/misc.py0000644000000000000000000000205500000000000017344 0ustar00"""Oxymorons. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: Oxymorons date: 2014-06-10 12:31:19 categories: writing --- Archaism. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "oxymorons.misc" msg = "'{}' is an oxymoron." oxymorons = [ "amateur expert", "increasingly less", "advancing backwards?", "alludes explicitly to", "explicitly alludes to", "totally obsolescent", "completely obsolescent", "generally always", "usually always", "increasingly less", "build down", "conspicuous absence", "exact estimate", "found missing", "intense apathy", "mandatory choice", "nonworking mother", "organized mess", # "pretty ugly", # "sure bet", # "executive secretary", ] return existence_check(text, oxymorons, err, msg, offset=1, join=True) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/psychology/__init__.py0000644000000000000000000000003100000000000020263 0ustar00"""Advice on science.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/psychology/misc.py0000644000000000000000000000255400000000000017473 0ustar00"""Psychological and psychiatric terms to avoid. --- layout: post source: Scott O. Lilienfeld, et al. source_url: http://dx.doi.org/10.3389/fpsyg.2015.01100 title: psychological and psychiatric terms to avoid date: 2014-06-10 12:31:19 categories: writing --- Psychological and psychiatric terms to avoid. """ from proselint.tools import existence_check, memoize, preferred_forms_check @memoize def check_lie_detector_test(text): """Suggest the preferred forms.""" err = "psychology.lie_detector" msg = "Polygraph machines measure arousal, not lying per se. Try {}." list = [ ["polygraph test", ["lie detector test"]], ["polygraph machine", ["lie detector machine"]], ] return preferred_forms_check(text, list, err, msg) @memoize def check_p_equals_zero(text): """Check for p = 0.000.""" err = "psychology.p_equals_zero" msg = "Unless p really equals zero, you should use more decimal places." list = [ "p = 0.00", "p = 0.000", "p = 0.0000", ] return existence_check(text, list, err, msg, join=True) @memoize def check_mental_telepathy(text): """Check for 'mental telepathy'.""" err = "psychology.mental_telepathy" msg = "This is redundant because all purported telepathy is mental." return existence_check(text, ["mental telepathy"], err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/redundancy/__init__.py0000644000000000000000000000002200000000000020217 0ustar00"""Redundancy.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/redundancy/misc.py0000644000000000000000000006073300000000000017432 0ustar00"""Redundancy.""" from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "redundancy.wallace" msg = "Redundancy. Use '{}' instead of '{}'." redundancies = [ ["rectangular", ["rectangular in shape"]], ["audible", ["audible to the ear"]], ] return preferred_forms_check(text, redundancies, err, msg) @memoize def check_garner(text): """Suggest the preferred forms. source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY """ err = "redundancy.garner" msg = "Redundancy. Use '{}' instead of '{}'." redundancies = [ ["adequate", ["adequate enough"]], ["admitted", ["self-admitted"]], ["affidavit", ["sworn affidavit"]], ["agreement", ["mutual agreement"]], ["alumnus", ["former alumnus"]], ["antithetical", ["directly antithetical"]], ["approximately", ["approximately about"]], ["associate", ["associate together(?: in groups)?"]], ["bivouac", ["temporary bivouac", "bivouac camp"]], ["blend", ["blend together"]], ["but", ["but nevertheless"]], ["charged with...", ["accused of a charge"]], ["circumstances of", ["circumstances surrounding"]], ["circumstances", ["surrounding circumstances"]], ["close", ["close proximity"]], ["collaborate", ["collaborate together"]], ["collaborator", ["fellow collaborator"]], ["collaborators", ["fellow collaborators"]], ["collocated", ["collocated together"]], ["colleagues", ["fellow colleagues"]], ["combine", ["combine together"]], ["complacent", ["self-complacent"]], ["confessed", ["self-confessed"]], ["connect", ["connect together"]], ["consensus", ["(?:general )?consensus of opinion"]], ["consolidate", ["consolidate together"]], ["continues to", ["still continues to"]], ["contradictory", ["mutually contradictory"]], ["cooperation", ["mutual cooperation"]], ["couple", ["couple together"]], ["crisis", ["serious crisis"]], ["eliminate", ["entirely eliminate"]], ["especially", ["most especially"]], ["fact", ["actual fact"]], ["facts", ["true facts"]], ["forecast", ["future forecast"]], ["founding fathers", ["founding forefathers"]], ["free", ["free and gratis"]], ["free", ["free gratis"]], ["full", ["completely full"]], ["fundamentals", ["basic fundamentals"]], ["gift", ["free gift"]], ["innovation", ["new innovation"]], ["interact", ["interact with each other"]], ["large", ["large-size"]], ["meld", ["meld together"]], ["merge", ["merge together"]], ["mingle", ["mingle together"]], ["mix", ["mix together"]], ["mutual feelings", ["mutual feelings for eachother"]], ["mutual respect", ["mutual respect for each other"]], ["native citizen", ["native-born citizen"]], ["necessity", ["absolute necessity"]], ["obvious", ["blatantly obvious"]], ["pause", ["pause for a moment"]], ["planning", ["advance planning"]], ["plans", ["future plans"]], ["pooled", ["pooled together"]], ["potable water", ["potable drinking water"]], ["potable water", ["potable drinking water"]], ["recruit", ["new recruit"]], ["reelected", ["reelected for another term"]], ["refer", ["refer back"]], ["regress", ["regress back"]], ["repay them", ["repay them back"]], ["repay", ["repay back"]], ["repeat", ["repeat again"]], ["repeat", ["repeat back"]], ["repeat", ["repeat the same"]], ["repeated", ["repeated the same"]], ["reprieve", ["temporary reprieve"]], ["respite", ["brief respite"]], ["retirement", ["retiral", "retiracy"]], ["retreat", ["retreat back"]], ["return", ["return back"]], ["scrutinize", ["closely scrutinize"]], ["software", ["software program"]], ["surrounded", ["surrounded on all sides"]], ["the nation", ["the whole entire nation"]], ["throughout the", ["throughout the entire"]], ["timpani", ["timpani drum"]], ["twins", ["pair of twins"]], ["vacancy", ["unfilled vacancy"]], ["various", ["various different"]], ["veteran", ["former veteran"]], ["visible", ["visible to the eye"]], ["vocation", ["professional vocation"]], ["while", ["while at the same time"]], ] return preferred_forms_check(text, redundancies, err, msg) @memoize def check_nordquist(text): """Suggest the preferred forms. source: Richard Nordquist source_url: http://grammar.about.com/bio/Richard-Nordquist-22176.htm """ err = "redundancy.nordquist" msg = "Redundancy. Use '{}' instead of '{}'." redundancies = [ ["essential", ["absolutely essential"]], ["necessary", ["absolutely necessary"]], ["a.m.", ["a.m. in the morning"]], ["p.m.", ["p.m. at night"]], ] return preferred_forms_check(text, redundancies, err, msg) @memoize def check_atd(text): """Check for redundancies from After the Deadline.""" err = "after_the_deadline.redundancy" msg = "Redundancy. Use '{}' instead of '{}'." redundancies = [ ["Bō", ["Bo Staff"]], ["Challah", ["Challah bread"]], ["Hallah", ["Hallah bread"]], ["Challah", ["Challah bread"]], ["I", ["I myself", "I personally"]], ["Mount Fuji", ["Mount Fujiyama"]], ["Milky Way", ["Milky Way galaxy"]], ["Rio Grande", ["Rio Grande river"]], ["adage", ["old adage"]], ["add", ["add a further", "add an additional"]], ["advance", ["advance forward"]], ["alternative", ["alternative choice"]], ["amaretto", ["amaretto almond"]], ["annihilate", ["completely annihilate"]], ["anniversary", ["annual anniversary"]], ["anonymous", ["unnamed anonymous"]], ["as", ["equally as"]], ["ascend", ["ascend up"]], ["ask", ["ask the question"]], ["assemble", ["assemble together"]], ["at present the", ["at the present time the"]], ["at this point", ["at this point in time"]], ["attach", ["attach together"]], ["autumn", ["autumn season"]], ["bald", ["bald-headed"]], ["balsa", ["balsa wood"]], ["belongings", ["personal belongings"]], ["benefits", ["desirable benefits"]], ["bento", ["bento box"]], ["best", ["best ever"]], ["bit", ["tiny bit"]], ["blend", ["blend together"]], ["bond", ["common bond"]], ["bonus", ["added bonus", "extra bonus"]], ["bouquet", ["bouquet of flowers"]], ["breakthrough", ["major breakthrough"]], ["bride", ["new bride"]], ["brief", ["brief in duration"]], ["bruin", ["bruin bear"]], ["hot", ["burning hot"]], ["cacophony", ["cacophony of sound"]], ["cameo", ["brief cameo", "cameo appearance"]], ["cancel", ["cancel out"]], ["cash", ["cash money"]], ["chai", ["chai tea"]], ["chance", ["random chance"]], ["charm", ["personal charm"]], ["circle", ["circle around", "round circle"]], ["circulate", ["circulate around"]], ["classify", ["classify into groups"]], ["classmates", ["fellow classmates"]], ["cliche", ["old cliche", "overused cliche"]], ["climb", ["climb up"]], ["clock", ["time clock"]], ["collaborate", ["collaborate together"]], ["collaboration", ["joint collaboration"]], ["colleague", ["fellow colleague"]], ["combine", ["combine together"]], ["commute", ["commute back and forth"]], ["compete", ["compete with each other"]], ["comprise", ["comprise of"]], ["comprises", ["comprises of"]], ["conceived", ["first conceived"]], ["conclusion", ["final conclusion"]], ["confer", ["confer together"]], ["confrontation", ["direct confrontation"]], # ["confused", ["confused state"]], ["connect", ["connect together", "connect up"]], ["consensus", ["consensus of opinion", "general consensus"]], ["consult", ["consult with"]], ["conversation", ["oral conversation"]], ["cool", ["cool down"]], ["cooperate", ["cooperate together"]], ["cooperation", ["mutual cooperation"]], ["copy", ["duplicate copy"]], ["core", ["inner core"]], ["cost", ["cost the sum of"]], ["could", ["could possibly"]], ["coupon", ["money-saving coupon"]], ["created", ["originally created"]], ["crisis", ["crisis situation"]], ["crouch", ["crouch down"]], ["currently", ["now currently"]], ["custom", ["old custom", "usual custom"]], ["danger", ["serious danger"]], ["dates", ["dates back"]], ["decision", ["definite decision"]], ["depreciate", ["depreciate in value"]], ["descend", ["descend down"]], ["destroy", ["totally destroy"]], ["destroyed", ["completely destroyed"]], ["destruction", ["total destruction"]], ["details", ["specific details"]], ["dilemma", ["difficult dilemma"]], ["disappear", ["disappear from sight"]], ["discovered", ["originally discovered"]], ["dive", ["dive down"]], ["done", ["over and done with"]], ["drawing", ["illustrated drawing"]], ["drop", ["drop down"]], ["dune", ["sand dune"]], ["during", ["during the course of"]], ["dwindle", ["dwindle down"]], ["dwindled", ["dwindled down"]], ["every", ["each and every"]], ["earlier", ["earlier in time"]], ["eliminate", ["completely eliminate", "eliminate altogether", "entirely eliminate"]], ["ember", ["glowing ember"]], ["embers", ["burning embers"]], ["emergency", ["emergency situation", "unexpected emergency"]], ["empty", ["empty out"]], ["enclosed", ["enclosed herein"]], ["end", ["final end"]], ["engulfed", ["completely engulfed"]], ["enter", ["enter in", "enter into"]], ["equal", ["equal to one another"]], ["eradicate", ["eradicate completely"]], ["essential", ["absolutely essential"]], ["estimated at", ["estimated at about", "estimated at approximately", "estimated at around"]], ["etc.", ["and etc."]], ["evolve", ["evolve over time"]], ["exaggerate", ["over exaggerate"]], ["exited", ["exited from"]], ["experience", ["actual experience", "past experience"]], ["experts", ["knowledgeable experts"]], ["extradite", ["extradite back"]], ["face the consequences", ["face up to the consequences"]], ["face the fact", ["face up to the fact"]], ["face the challenge", ["face up to the challenge"]], ["face the problem", ["face up to the problem"]], ["facilitate", ["facilitate easier"]], ["fact", ["established fact"]], ["facts", ["actual facts", "hard facts", "true facts"]], ["fad", ["passing fad"]], ["fall", ["fall down"]], ["fall", ["fall season"]], ["feat", ["major feat"]], ["feel", ["feel inside"]], ["feelings", ["inner feelings"]], ["few", ["few in number"]], ["filled", ["completely filled", "filled to capacity"]], ["first", ["first of all"]], ["first time", ["first time ever"]], ["fist", ["closed fist"]], ["fly", ["fly through the air"]], ["focus", ["focus in", "main focus"]], ["follow", ["follow after"]], ["for example", ["as for example"]], # ["foremost", ["first and foremost"]], ["forever", ["forever and ever"]], ["free", ["for free"]], ["friend", ["personal friend"]], ["friendship", ["personal friendship"]], ["full", ["full to capacity"]], ["fundamentals", ["basic fundamentals"]], ["fuse", ["fuse together"]], ["gather", ["gather together", "gather up"]], ["get up", ["get up on his feet", "get up on your feet"]], ["gift", ["free gift"]], ["gifts", ["free gifts"]], ["goal", ["ultimate goal"]], # ["graduate", ["former graduate"]], ["grow", ["grow in size"]], ["guarantee", ["absolute guarantee"]], ["gunman", ["armed gunman"]], ["gunmen", ["armed gunmen"]], ["habitat", ["native habitat"]], ["had done", ["had done previously"]], ["halves", ["two equal halves"]], # ["has", ["has got"]], # ["have", ["have got"]], ["haven", ["safe haven"]], # ["he", ["he himself"]], ["heat", ["heat up"]], ["history", ["past history"]], ["hoist", ["hoist up"]], ["hole", ["empty hole"]], ["honcho", ["head honcho"]], ["ice", ["frozen ice"]], ["ideal", ["perfect ideal"]], ["identical", ["same identical"]], ["identification", ["positive identification"]], ["imports", ["foreign imports"]], ["impulse", ["sudden impulse"]], ["in fact", ["in actual fact"]], ["in the yard", ["outside in the yard"]], ["inclusive", ["all inclusive"]], ["incredible", ["incredible to believe"]], ["incumbent", ["present incumbent"]], # ["indicted", ["indicted on a charge"]], ["industry", ["private industry"]], ["injuries", ["harmful injuries"]], ["innovation", ["new innovation"]], ["innovative", ["innovative new", "new innovative"]], # ["input", ["input into"]], ["instinct", ["natural instinct", "naturally instinct"]], ["integrate", ["integrate together", "integrate with each other"]], ["interdependent", ["interdependent on each other", "mutually interdependent"]], ["introduced", ["introduced for the first time"]], ["invention", ["new invention"]], ["kneel", ["kneel down"]], ["knots", ["knots per hour"]], # ["last", ["last of all"]], # ["later", ["later time"]], ["lift", ["lift up"]], ["lingers", ["still lingers"]], ["look to the future", ["look ahead to the future"]], ["love triangle", ["three-way love triangle"]], ["maintained", ["constantly maintained"]], ["manually", ["manually by hand"]], ["marina", ["boat marina"]], ["may", ["may possibly"]], ["meet", ["meet together", "meet with each other"]], ["memories", ["past memories"]], ["merge", ["merge together"]], ["merged", ["merged together"]], ["meshed", ["meshed together"]], ["midnight", ["twelve midnight"]], ["migraine", ["migraine headache"]], ["minestrone", ["minestrone soup"]], ["mix", ["mix together"]], ["moment", ["brief moment", "moment in time"]], ["monopoly", ["complete monopoly"]], ["mural", ["wall mural"]], ["mutual respect", ["mutual respect for each other"]], ["mutually dependent", ["mutually dependent on each other"]], ["mystery", ["unsolved mystery"]], # ["naked", ["bare naked"]], ["nape", ["nape of her neck"]], ["necessary", ["absolutely necessary"]], ["never", ["never at any time"]], ["noon", ["12 noon", "12 o'clock noon", "high noon", "twelve noon"]], ["nostalgia", ["nostalgia for the past"]], ["number of", ["number of different"]], ["opening", ["exposed opening"]], ["my opinion", ["my personal opinion"]], ["opposites", ["exact opposites", "polar opposites"]], ["opposite", ["exact opposite", "polar opposite"]], ["orbits", ["orbits around"]], ["outcome", ["final outcome"]], ["panacea", ["universal panacea"]], ["pending", ["now pending"]], ["penetrate", ["penetrate through"]], ["persists", ["still persists"]], ["pioneer", ["old pioneer"]], ["plan", ["plan ahead", "plan in advance", "proposed plan"]], ["planning", ["advance planning", "forward planning"]], ["plans", ["future plans"]], ["plan", ["future plan"]], ["point", ["point in time"]], ["point", ["sharp point"]], ["postpone", ["postpone until later"]], ["pouring rain", ["pouring down rain"]], ["preview", ["advance preview"]], ["previously listed", ["previously listed above"]], ["probed", ["probed into"]], ["proceed", ["proceed ahead"]], ["prosthesis", ["artificial prosthesis"]], # ["protrude", ["protrude out"]], ["proverb", ["old proverb"]], # ["proximity", ["close proximity"]], ["put off", ["put off until later"]], # ["raise", ["raise up"]], ["re-elect", ["re-elect for another term"]], ["reason is", ["reason is because"]], ["recur", ["recur again"]], ["recurrence", ["future recurrence"]], ["refer", ["refer back"]], ["reflect", ["reflect back"]], # ["relevant", ["highly relevant"]], ["remain", ["continue to remain"]], ["remains", ["still remains"]], ["replica", ["exact replica"]], ["reply", ["reply back"]], # ["requirements", ["necessary requirements"]], ["reservations", ["advance reservations"]], ["retreat", ["retreat back"]], ["revert", ["revert back"]], ["round", ["round in shape"]], ["rule of thumb", ["rough rule of thumb"]], ["rumor", ["unconfirmed rumor"]], ["rustic", ["rustic country"]], ["same", ["exact same", "precise same", "same exact"]], ["sanctuary", ["safe sanctuary"]], ["satisfaction", ["full satisfaction"]], ["scrutinize", ["scrutinize in detail"]], ["scrutiny", ["careful scrutiny", "close scrutiny"]], ["secret", ["secret that cannot be told"]], ["seek", ["seek to find"]], ["separated", ["separated apart from each other"]], ["share", ["share together"]], ["shiny", ["shiny in appearance"]], ["sincere", ["truly sincere"]], ["sink", ["sink down"]], ["skipped", ["skipped over"]], # ["slow", ["slow speed"]], # ["small", ["small size"]], ["soft", ["soft in texture", "soft to the touch"]], ["sole", ["sole of the foot"]], ["some time", ["some time to come"]], ["speck", ["small speck"]], ["speed", ["rate of speed"]], ["spell out", ["spell out in detail"]], ["spiked", ["spiked upward", "spiked upwards"]], ["spring", ["spring season"]], ["stranger", ["anonymous stranger"]], ["studio audience", ["live studio audience"]], ["subway", ["underground subway"]], ["sufficient", ["sufficient enough"]], ["summer", ["summer season"]], ["sure", ["absolutely sure"]], ["surprise", ["unexpected surprise"]], ["surround", ["completely surround"]], ["surrounded", ["surrounded on all sides"]], ["tall", ["tall in height", "tall in stature"]], ["telepathy", ["mental telepathy"]], ["ten", ["ten in number"]], ["these", ["these ones"]], # ["they", ["they themselves"]], ["those", ["those ones"]], ["trench", ["open trench"]], ["truth", ["honest truth"]], ["tundra", ["frozen tundra"]], ["ultimatum", ["final ultimatum"]], # ["undeniable", ["undeniable truth"]], ["undergraduate", ["undergraduate student"]], # ["unintentional", ["unintentional mistake"]], ["vacillate", ["vacillate back and forth"]], ["veteran", ["former veteran"]], ["visible", ["visible to the eye"]], ["warn", ["warn in advance"]], ["warning", ["advance warning"]], ["water heater", ["hot water heater"]], ["in which we live", ["in which we live in"]], ["winter", ["winter season"]], ["witness", ["live witness"]], ["yakitori", ["yakitori chicken"]], ["yerba mate", ["yerba mate tea"]], ["yes", ["affirmative yes"]], ] return preferred_forms_check(text, redundancies, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/redundancy/ras_syndrome.py0000644000000000000000000000243200000000000021174 0ustar00"""Redundant Acronym Syndrome (RAS) syndrome.""" from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "garner.redundancy.ras" msg = "RAS syndrome. Use '{}' instead of '{}'." redundancies = [ ["ABM", ["ABM missile"]], ["ACT", ["ACT test"]], ["ABMs", ["ABM missiles"]], ["ABS", ["ABS braking system"]], ["ATM", ["ATM machine"]], ["CD", ["CD disc"]], ["CPI", ["CPI Index"]], ["GPS", ["GPS system"]], ["GUI", ["GUI interface"]], ["HIV", ["HIV virus"]], ["ISBN", ["ISBN number"]], ["LCD", ["LCD display"]], ["PDF", ["PDF format"]], ["PIN", ["PIN number"]], ["RAS", ["RAS syndrome"]], ["RIP", ["RIP in peace"]], ["RSVP", ["please RSVP"]], ["SALT", ["SALT talks"]], ["SAT", ["SAT test"]], ["UPC", ["UPC codes"]], ] return preferred_forms_check(text, redundancies, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/security/__init__.py0000644000000000000000000000002000000000000017730 0ustar00"""Security.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/security/credit_card.py0000644000000000000000000000116700000000000020451 0ustar00"""Credit card number printed. --- layout: post source: ??? source_url: ??? title: credit card number printed date: 2014-06-10 12:31:19 categories: writing --- Credit card number printed. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "security.credit_card" msg = "Don't put credit card numbers in plain text." credit_card_numbers = [ r"4\d{15}", r"5[1-5]\d{14}", r"3[4,7]\d{13}", r"3[0,6,8]\d{12}", r"6011\d{12}", ] return existence_check(text, credit_card_numbers, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/security/password.py0000644000000000000000000000124600000000000020046 0ustar00"""Password in plain text. --- layout: post source: ??? source_url: ??? title: password in plain text date: 2014-06-10 12:31:19 categories: writing --- Don't put pass """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "security.password" msg = "Don't put passwords in plain text." pwd_regex = r"[:]? [\S]{6,30}" password = [ f"the password is{pwd_regex}", f"my password is{pwd_regex}", f"the password's{pwd_regex}", f"my password's{pwd_regex}", f"^[pP]assword{pwd_regex}", ] return existence_check(text, password, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/sexism/__init__.py0000644000000000000000000000001600000000000017376 0ustar00"""Sexism.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/sexism/misc.py0000644000000000000000000000475700000000000016612 0ustar00"""Sexism. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: sexism date: 2014-06-10 12:31:19 categories: writing --- Points out sexist language. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "sexism.misc" msg = "Gender bias. Use '{}' instead of '{}'." sexism = [ ["anchor", ["anchorman", "anchorwoman"]], ["chair", ["chairman", "chairwoman"]], ["drafter", ["draftman", "draftwoman"]], ["ombuds", ["ombudsman", "ombudswoman"]], ["tribe member", ["tribesman", "tribeswoman"]], ["police officer", ["policeman", "policewoman"]], ["firefighter", ["fireman", "firewoman"]], ["mail carrier", ["mailman", "mailwoman"]], ["history", ["herstory"]], ["women", ["womyn"]], ["poet", ["poetess"]], ["author", ["authoress"]], ["waiter", ["waitress"]], ["lawyer", ["lady lawyer"]], ["doctor", ["woman doctor"]], ["bookseller", ["female booksalesman"]], ["air pilot", ["female airman"]], ["executor", ["executrix"]], ["prosecutor", ["prosecutrix"]], ["testator", ["testatrix"]], ["husband and wife", ["man and wife"]], ["chairs", ["chairmen and chairs"]], ["men and women", ["men and girls"]], ["comedian", ["comedienne"]], ["confidant", ["confidante"]], ["scientist", ["woman scientist"]], ["scientists", ["women scientists"]] # ["hero", ["heroine"]] ] errors = preferred_forms_check(text, sexism, err, msg, ignore_case=False) msg = "Not a preferred form. Use '{}' instead of '{}'." pref = [ ["anchor", ["anchorperson"]], ["chair", ["chairperson"]], ["drafter", ["draftperson"]], ["ombuds", ["ombudsperson"]], ["tribe member", ["tribesperson"]], ["police officer", ["policeperson"]], ["firefighter", ["fireperson"]], ["mail carrier", ["mailperson"]], ] for x in preferred_forms_check(text, pref, err, msg, ignore_case=False): errors.append(x) return errors ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/skunked_terms/__init__.py0000644000000000000000000000003300000000000020743 0ustar00"""Avoid skunked terms.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/skunked_terms/misc.py0000644000000000000000000000134200000000000020143 0ustar00"""Skunked terms. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: Skunked terms. date: 2014-06-10 12:31:19 categories: writing --- Archaism. """ from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "skunked_terms.misc" msg = """'{}' is a bit of a skunked term, impossible to use without issue. Find some other way to say it.""" skunked_terms = [ "bona fides", "deceptively", "decimate", "effete", "fulsome", "hopefully", "impassionate", "Thankfully,", ] return existence_check(text, skunked_terms, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.182823 proselint-0.13.0/proselint/checks/spelling/__init__.py0000644000000000000000000000002000000000000017676 0ustar00"""Spelling.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/spelling/able_atable.py0000644000000000000000000000712700000000000020371 0ustar00"""-able vs. -atable.""" from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """-able vs. -atable.""" err = "spelling.able_atable" msg = "-able vs. -atable. '{}' is the preferred spelling." preferences = [ ["abbreviable", ["abbreviatable"]], ["abdicable", ["abdicatable"]], ["abrogable", ["abrogatable"]], ["accommodable", ["accommodatable"]], ["accumulable", ["accumulatable"]], ["activable", ["activatable"]], ["administrable", ["administratable"]], ["adulterable", ["adulteratable"]], ["affiliable", ["affiliatable"]], ["aggregable", ["aggregatable"]], ["agitable", ["agitatable"]], ["alienable", ["alienatable"]], ["alleviable", ["alleviatable"]], ["allocable", ["allocatable"]], ["ameliorable", ["amelioratable"]], ["annihilable", ["annihilatable"]], ["appreciable", ["appreciatable"]], ["appropriable", ["appropriatable"]], ["arbitrable", ["arbitratable"]], ["articulable", ["articulatable"]], ["calculable", ["calculatable"]], ["communicable", ["communicatable"]], ["compensable", ["compensatable"]], ["confiscable", ["confiscatable"]], ["corroborable", ["corroboratable"]], ["cultivable", ["cultivatable"]], ["delegable", ["delegatable"]], ["delineable", ["delineatable"]], ["demonstrable", ["demonstratable"]], ["detonable", ["detonatable"]], ["differentiable", ["differentiatable"]], ["eradicable", ["eradicatable"]], ["evacuable", ["evacuatable"]], ["evaluable", ["evaluatable"]], ["expropriable", ["expropriatable"]], ["generable", ["generatable"]], ["indicable", ["indicatable"]], ["inebriable", ["inebriatable"]], ["inextirpable", ["inextirpatable"]], ["inextricable", ["inextricatable"]], ["infatuable", ["infatuatable"]], ["infuriable", ["infuriatable"]], ["invalidable", ["invalidatable"]], ["investigable", ["investigatable"]], ["isolable", ["isolatable"]], ["litigable", ["litigatable"]], ["manipulable", ["manipulatable"]], ["medicable", ["medicatable"]], ["navigable", ["navigatable"]], ["obligable", ["obligatable"]], ["obviable", ["obviatable"]], ["operable", ["operatable"]], ["originable", ["originatable"]], ["participable", ["participatable"]], ["penetrable", ["penetratable"]], ["perpetrable", ["perpetratable"]], ["perpetuable", ["perpetuatable"]], ["predicable", ["predicatable"]], ["propagable", ["propagatable"]], ["regulable", ["regulatable"]], ["replicable", ["replicatable"]], ["repudiable", ["repudiatable"]], ["segregable", ["segregatable"]], ["separable", ["separatable"]], ["subjugable", ["subjugatable"]], ["vindicable", ["vindicatable"]], ["violable", ["violatable"]], ["vitiable", ["vitiatable"]] ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/spelling/able_ible.py0000644000000000000000000001625600000000000020057 0ustar00"""-able vs. -ible.""" from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """-able vs. -ible.""" err = "spelling.able_ible" msg = "-able vs. -ible. '{}' is the preferred spelling." preferences = [ ["actionable", ["actionible"]], ["addable", ["addible"]], ["admittable", ["admittible"]], ["advisable", ["advisible"]], ["affectable", ["affectible"]], ["allegeable", ["allegeible"]], ["analyzable", ["analyzible"]], ["annexable", ["annexible"]], ["arrestable", ["arrestible"]], ["ascendable", ["ascendible"]], ["assertable", ["assertible"]], ["assessable", ["assessible"]], ["averageable", ["averageible"]], ["bailable", ["bailible"]], ["blamable", ["blamible"]], ["changeable", ["changeible"]], ["chargeable", ["chargeible"]], ["circumscribable", ["circumscribible"]], ["commensurable", ["commensurible"]], ["committable", ["committible"]], ["condensable", ["condensible"]], ["connectable", ["connectible"]], ["contestable", ["contestible"]], ["contractable", ["contractible"]], ["conversable", ["conversible"]], ["convictable", ["convictible"]], ["correctable", ["correctible"]], ["definable", ["definible"]], ["detectable", ["detectible"]], ["diagnosable", ["diagnosible"]], ["discussable", ["discussible"]], ["endorsable", ["endorsible"]], ["enforceable", ["enforceible"]], ["evadable", ["evadible"]], ["excisable", ["excisible"]], ["excludable", ["excludible"]], ["expandable", ["expandible"]], ["extendable", ["extendible"]], ["extractable", ["extractible"]], ["ignitable", ["ignitible"]], ["immovable", ["immovible"]], ["improvable", ["improvible"]], ["includable", ["includible"]], ["inferable", ["inferible"]], ["inventable", ["inventible"]], ["investable", ["investible"]], ["lapsable", ["lapsible"]], ["lovable", ["lovible"]], ["mixable", ["mixible"]], ["movable", ["movible"]], ["noticeable", ["noticeible"]], ["offendable", ["offendible"]], ["patentable", ["patentible"]], ["persuadable", ["persuadible"]], ["preventable", ["preventible"]], ["processable", ["processible"]], ["protectable", ["protectible"]], ["ratable", ["ratible"]], ["redressable", ["redressible"]], ["referable", ["referible"]], ["retractable", ["retractible"]], ["revisable", ["revisible"]], ["rinsable", ["rinsible"]], ["salable", ["salible"]], ["suspendable", ["suspendible"]], ["tractable", ["tractible"]], ["transferable", ["transferible"]], ["transmittable", ["transmittible"]], ["willable", ["willible"]], ["accessible", ["accessable"]], ["adducible", ["adducable"]], ["admissible", ["admissable"]], ["audible", ["audable"]], ["avertible", ["avertable"]], ["collapsible", ["collapsable"]], ["collectible", ["collectable"]], ["combustible", ["combustable"]], ["compactible", ["compactable"]], ["compatible", ["compatable"]], ["comprehensible", ["comprehensable"]], ["compressible", ["compressable"]], ["concussible", ["concussable"]], ["conductible", ["conductable"]], ["contemptible", ["contemptable"]], ["controvertible", ["controvertable"]], ["convertible", ["convertable"]], ["corrodible", ["corrodable"]], ["corruptible", ["corruptable"]], ["credible", ["credable"]], ["deducible", ["deducable"]], ["deductible", ["deductable"]], ["defeasible", ["defeasable"]], ["defensible", ["defensable"]], ["descendible", ["descendable"]], ["destructible", ["destructable"]], ["diffusible", ["diffusable"]], ["digestible", ["digestable"]], ["discernible", ["discernable"]], ["dismissible", ["dismissable"]], ["divisible", ["divisable"]], ["edible", ["edable"]], ["educible", ["educable"]], ["eligible", ["eligable"]], ["erodible", ["erodable"]], ["exhaustible", ["exhaustable"]], ["expressible", ["expressable"]], ["fallible", ["fallable"]], ["feasible", ["feasable"]], ["flexible", ["flexable"]], ["forcible", ["forcable"]], ["fusible", ["fusable"]], ["gullible", ["gullable"]], ["horrible", ["horrable"]], ["impressible", ["impressable"]], ["inadmissible", ["inadmissable"]], ["incorrigible", ["incorrigable"]], ["indelible", ["indelable"]], ["inexpressible", ["inexpressable"]], ["intelligible", ["intelligable"]], ["interfusible", ["interfusable"]], ["invincible", ["invincable"]], ["irascible", ["irascable"]], ["irresistible", ["irresistable"]], ["legible", ["legable"]], ["negligible", ["negligable"]], ["omissible", ["omissable"]], ["oppressible", ["oppressable"]], ["ostensible", ["ostensable"]], ["perceptible", ["perceptable"]], ["perfectible", ["perfectable"]], ["permissible", ["permissable"]], ["plausible", ["plausable"]], ["possible", ["possable"]], ["producible", ["producable"]], ["reducible", ["reducable"]], ["remissible", ["remissable"]], ["reprehensible", ["reprehensable"]], ["repressible", ["repressable"]], ["resistible", ["resistable"]], ["responsible", ["responsable"]], ["reversible", ["reversable"]], ["revertible", ["revertable"]], ["risible", ["risable"]], ["seducible", ["seducable"]], ["sensible", ["sensable"]], ["submersible", ["submersable"]], ["submergible", ["submergable"]], ["suggestible", ["suggestable"]], ["suppressible", ["suppressable"]], ["susceptible", ["susceptable"]], ["terrible", ["terrable"]], ["transfusible", ["transfusable"]], ["transmissible", ["transmissable"]], ["uncollectible", ["uncollectable"]], ["vendible", ["vendable"]], ["visible", ["visable"]] ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/spelling/athletes.py0000644000000000000000000000262400000000000017764 0ustar00"""Misspellings. --- layout: post source: The Wall Street Journal source_url: http://on.wsj.com/1rksm8k title: misspellings date: 2014-06-10 12:31:19 categories: writing --- Points out misspellings. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "spelling.athletes" msg = "Misspelling of athlete's name. '{}' is the preferred form." misspellings = [ ["Dwyane Wade", ["Dwayne Wade"]], ["Miikka Kiprusoff", ["Mikka Kiprusoff"]], ["Mark Buehrle", ["Mark Buerhle"]], ["Skylar Diggins", ["Skyler Diggins"]], ["Agnieszka Radwanska", ["Agnieska Radwanska"]], ["J.J. Redick", ["J.J. Reddick"]], ["Manny Pacquiao", ["Manny Packquaio"]], ["Antawn Jamison", ["Antwan Jamison"]], ["Cal Ripken", ["Cal Ripkin"]], ["Jhonny Peralta", ["Johnny Peralta"]], ["Monta Ellis", ["Monte Ellis"]], ["Alex Rodriguez", ["Alex Rodriquez"]], ["Mark Teixeira", ["Mark Texeira"]], ["Brett Favre", ["Brett Farve"]], ["Torii Hunter", ["Tori Hunter"]], ["Stephen Curry", ["Stephon Curry"]], ["Mike Krzyzewski", ["Mike Kryzewski"]], ] return preferred_forms_check(text, misspellings, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/spelling/em_im_en_in.py0000644000000000000000000000424000000000000020405 0ustar00"""Em vs. im, en vs. in.""" from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """em- vs. en-, im- vs. in-.""" err = "spelling.em_im_en_in" msg = "em-, im-, en-, and in-. '{}' is the preferred spelling." preferences = [ ["embalm", ["imbalm"]], ["embark", ["imbark"]], ["embed", ["imbed"]], ["embitter", ["imbitter"]], ["emblaze", ["imblaze"]], ["embody", ["imbody"]], ["embolden", ["imbolden"]], ["embosom", ["imbosom"]], ["embower", ["imbower"]], ["embrown", ["imbrown"]], ["empanel", ["impanel"]], ["empower", ["impower"]], ["encage", ["incage"]], ["encapsulate", ["incapsulate"]], ["encase", ["incase"]], ["enclasp", ["inclasp"]], ["encumber", ["incumber"]], ["encumbrance", ["incumbrance"]], ["endow", ["indow"]], ["endowment", ["indowment"]], ["endue", ["indue"]], ["enfold", ["infold"]], ["engraft", ["ingraft"]], ["engulf", ["ingulf"]], ["enlace", ["inlace"]], ["enmesh", ["inmesh"]], ["ensheathe", ["insheathe"]], ["enshrine", ["inshrine"]], ["ensnare", ["insnare"]], ["ensoul", ["insoul"]], ["ensphere", ["insphere"]], ["enthrall", ["inthrall"]], ["enthrone", ["inthrone"]], ["entitle", ["intitle"]], ["entomb", ["intomb"]], ["entreat", ["intreat"]], ["entrench", ["intrench"]], ["entrust", ["intrust"]], ["entwine", ["intwine"]], ["entwist", ["intwist"]], ["enwind", ["inwind"]], ["enwrap", ["inwrap"]], ["enwreathe", ["inwreathe"]], ["imbrue", ["embrue"]], ["impale", ["empale"]], ["impoverish", ["empoverish"]], ["inflame", ["enflame"]], ["ingrain", ["engrain"]], ["inure", ["enure"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/spelling/er_or.py0000644000000000000000000000272300000000000017261 0ustar00"""-er vs. -or.""" from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """-er vs. -or.""" err = "spelling.er_or" msg = "-er vs. -or. '{}' is the preferred spelling." preferences = [ ["abductor", ["abducter"]], ["abettor", ["abbeter"]], ["acquirer", ["acquiror"]], ["adapter", ["adaptor"]], ["collector", ["collecter"]], ["conjurer", ["conjuror"]], ["corrupter", ["corruptor"]], ["digester", ["digestor"]], ["dispenser", ["dispensor"]], ["distributor", ["distributer"]], ["endorser", ["endorsor"]], ["eraser", ["erasor"]], ["idolater", ["idolator"]], ["impostor", ["imposter"]], ["infiltrator", ["infiltrater"]], ["investor", ["invester"]], ["manipulator", ["manipulater"]], ["mortgagor", ["mortgager"]], ["persecutor", ["persecuter"]], ["promoter", ["promotor"]], ["promoter", ["promotor"]], ["purveyor", ["purveyer"]], ["requester", ["requestor"]], ["reviser", ["revisor"]], ["surveyor", ["surveyer"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/spelling/in_un.py0000644000000000000000000000077000000000000017263 0ustar00"""in- vs. un-.""" from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """in- vs un-.""" err = "spelling.in_un" msg = "in- vs. un-. '{}' is the preferred spelling." preferences = [ ["inadvisable", ["unadvisable"]], ["inalienable", ["unalienable"]], ["inexpressive", ["unexpressive"]], ["infeasible", ["unfeasible"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/spelling/misc.py0000644000000000000000000001426200000000000017107 0ustar00"""Misspellings. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: misspellings date: 2014-06-10 12:31:19 categories: writing --- Points out misspellings. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "spelling.misc" msg = "Misspelling. '{}' is the preferred spelling." misspellings = [ ["a lot", ["alot"]], ["academically", ["academicly"]], ["accidentally", ["accidently"]], ["accommodable", ["accomodatable"]], ["anilingus", ["analingus"]], ["aren't i", ["amn't i"]], ["aren't i", ["an't i"]], ["barbed wire", ["bob wire"]], ["chargeable", ["chargable"]], ["chiffonier", ["chiffonnier"]], ["chintzy", ["chinchy"]], ["chipotle", ["chipolte"]], ["chlorophyll", ["chlorophyl"]], ["chocolaty", ["chocolatey"]], ["chronicle", ["chronical"]], ["chronicles", ["chronicals"]], ["coleslaw", ["coldslaw"]], ["choosy", ["choosey"]], ["cummerbund", ["kummerbund"]], ["financeable", ["financable"]], ["fluorescent", ["flourescent"]], ["fluoridation", ["flouridation"]], ["fluoride", ["flouride"]], ["foreclose", ["forclose"]], ["forswear", ["foreswear"]], ["free rein", ["free reign"]], ["gist", ["jist"]], ["glamour", ["glamor"]], ["granddad", ["grandad"]], ["grandpa", ["granpa"]], ["highfalutin", ["highfaluting", "highfalutin'", "hifalutin"]], ["Hippocratic", ["hypocratic"]], ["hirable", ["hireable"]], ["holistic", ["wholistic"]], ["ideology", ["idealogy"]], ["idiosyncrasy", ["ideosyncracy"]], ["improvise", ["improvize"]], ["incurrence", ["incurment"]], ["inevitability", ["inevitableness"]], ["innovate", ["inovate"]], ["inoculation", ["innoculation", "inocculation"]], ["integral", ["intergral"]], ["inundate", ["innundate"]], ["inundated", ["innundated"]], ["inundates", ["innundates"]], ["inundating", ["innundating"]], ["iridescent", ["irridescent"]], ["irrelevant", ["irrevelant"]], ["jujitsu", ["jiujutsu"]], ["kaleidoscope", ["kaleidascope"]], ["knickknack", ["knicknack"]], ["lassos", ["lassoes"]], ["lessee", ["leasee"]], ["lessor", ["leasor"]], ["liaison", ["liason"]], ["liaison", ["laison"]], ["lightning rod", ["lightening rod"]], ["struck by lightning", ["struck by lightening"]], ["liquefy", ["liquify"]], ["loathsome", ["loathesome"]], ["lodestar", ["loadstar"]], ["to make do", ["to make due"]], ["mademoiselle", ["madamoiselle"]], ["martial arts", ["marshall arts"]], ["court-martial", ["court marshall", "court-marshall"]], ["maelstrom", ["maelstorm"]], ["mafia", ["maffia"]], ["mafiosi", ["mafiosos"]], ["mangoes", ["mangos"]], ["marijuana", ["marihuana"]], ["marshmallow", ["marshmellow"]], ["martial law", ["marshall law"]], ["massacring", ["massacering", "massacreing"]], ["measles", ["measels"]], ["memento", ["momento"]], ["minuscule", ["miniscule"]], ["mileage", ["milage"]], ["milieu", ["mileau"]], ["millennium", ["millenium"]], ["millennia", ["millenia"]], ["millenniums", ["milleniums"]], ["millipede", ["millepede"]], ["millionaire", ["millionnaire"]], ["milquetoast", ["milktoast"]], ["mimicked", ["mimiced"]], ["mimicking", ["mimicing"]], ["misspelling", ["mispelling"]], ["mischievous", ["mischievious"]], ["moccasin", ["mocasin", "moccassin", "mocassin"]], ["monologue", ["monolog"]], ["monologuist", ["monologist"]], ["naphtha", ["naptha"]], ["navel orange", ["naval orange"]], ["neckwear", ["neckware"]], ["newsstand", ["newstand"]], ["non sequitur", ["non sequiter", "non sequitar", "non sequitor"]], ["nouveau riche", ["nouveau rich"]], ["occurred", ["occured"]], ["plantar fasciitis", ["planter fasciitis", "plantar fascitis"]], ["pledgeable", ["pledgable", "pledgeble"]], ["portentous", ["portentuous", "portentious"]], ["praying mantis", ["preying mantis"]], ["preestablished", ["prestablished"]], ["preexisting", ["prexisting"]], ["presumptuous", ["presumptious"]], ["rarefy", ["rarify"]], ["reckless", ["wreckless"]], ["remuneration", ["renumeration"]], ["restaurateur", ["restauranteur"]], ["retractable", ["retractible"]], ["reverie", ["revery"]], ["spicy", ["spicey"]], ["stupefy", ["stupify"]], ["subtly", ["subtley"]], ["till", ["\'till?"]], ["tinderbox", ["tenderbox"]], ["timpani", ["tympani"]], ["a timpani", ["a timpano"]], ["umpteenth", ["umteenth"]], ["verbiage", ["verbage"]], ] return preferred_forms_check(text, misspellings, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/terms/__init__.py0000644000000000000000000000001500000000000017217 0ustar00"""Terms.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/terms/animal_adjectives.py0000644000000000000000000000456000000000000021133 0ustar00"""Animal adjectives. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: animal words date: 2014-06-10 12:31:19 categories: writing --- Animal words. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "garner.animal_labels" msg = "There's a word for this: '{}'." preferences = [ ["accipitrine", ["hawk-like"]], ["anserine", ["goose-like"]], ["aquiline", ["eagle-like"]], ["avine", ["bird-like"]], ["cancrine", ["crab-like"]], ["hircine", ["goat-like"]], ["damine", ["deer-like"]], ["corvine", ["crow-like", "raven-like"]], ["crocodiline", ["crocodile-like"]], ["crotaline", ["rattlesnake-like"]], ["falconine", ["falcon-like"]], ["ferine", ["wild animal-like"]], ["hippopotamine", ["hippopotamus-like"]], ["hirundine", ["swallow-like"]], ["hystricine", ["porcupine-like"]], ["lacertine", ["lizard-like"]], ["laridine", ["gull-like"]], ["leporine", ["hare-like"]], ["lumbricine", ["earthworm-like"]], ["lupine", ["wolf-like"]], ["murine", ["mouse-like"]], ["ovine", ["sheep-like"]], ["pardine", ["leopard-like", "panther-like"]], ["passerine", ["sparrow-like"]], ["pavonine", ["peacock-like"]], ["picine", ["woodpecker-like"]], ["piscine", ["fish-like"]], ["ranine", ["frog-like"]], ["scolopendrine", ["centipede-like"]], ["soricine", ["shrew-like"]], ["struthionine", ["ostrich-like"]], ["suilline", ["swine-like"]], ["taurine", ["bull-like", "ox-like"]], ["tigrine", ["tiger-like"]], ["vespine", ["wasp-like"]], ["viperine", ["viper-like"]], ["vituline", ["calf-like", "veal-like"]], ["viverrine", ["mongoose-like"]], ["vulpine", ["fox-like"]], ["vulturine", ["vulture-like"]], ["zebrine", ["zebra-like"]], ["zibeline", ["sable-like"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/terms/denizen_labels.py0000644000000000000000000000635100000000000020447 0ustar00"""Denizen labels.""" from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms. source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY """ err = "terms.denizen_labels.garner" msg = "'{}' is the preferred denizen label." preferences = [ ["Afrikaner", ["Afrikaaner"]], ["Afrikaner", ["Afrikander"]], ["Alabamian", ["Alabaman"]], ["Albuquerquean", ["Albuquerquian"]], ["Anchorageite", ["Anchoragite"]], ["Angeleno", ["Los Angelean"]], ["Arizonan", ["Arizonian"]], ["Arkansan", ["Arkansawyer"]], ["Belarusian", ["Belarusan"]], ["Caymanian", ["Cayman Islander"]], ["Coloradan", ["Coloradoan"]], ["Fairbanksan", ["Fairbanksian"]], ["Fort Worthian", ["Fort Worther"]], ["Grenadan", ["Grenadian"]], ["Hong Konger", ["Hong Kongite", "Hong Kongian"]], ["Hoosier", ["Indianan", "Indianian"]], ["Illinoisan", ["Illinoisian"]], ["Iowan", ["Iowegian"]], ["Louisianian", ["Louisianan"]], ["Michigander", ["Michiganite", "Michiganian"]], ["Missourian", ["Missouran"]], ["Monegasque", ["Monacan"]], ["Neapolitan", ["Neopolitan"]], ["New Hampshirite", ["New Hampshireite", "New Hampshireman"]], ["New Jerseyan", ["New Jerseyite"]], ["New Orleanian", ["New Orleansian"]], ["Nutmegger", ["Connecticuter"]], ["Oklahoma Cityan", ["Oklahoma Citian"]], ["Oklahoman", ["Oklahomian"]], ["Seattleite", ["Seattlite"]], ["Surinamese", ["Surinamer"]], ["Tallahasseean", ["Tallahassean"]], ["Tennessean", ["Tennesseean"]], ["Tusconan", ["Tusconian", "Tusconite"]], ["Utahn", ["Utahan"]], ["Saudi", ["Saudi Arabian"]], ] return preferred_forms_check(text, preferences, err, msg) @memoize def check_denizen_labels_norris(text): """Suggest the preferred forms. source: Mary Norris source_url: http://nyr.kr/1rGienj """ err = "terms.denizen_labels.norris" msg = "Would you like '{}'?" preferences = [ ["Mancunian", ["Manchesterian"]], ["Mancunians", ["Manchesterians"]], ["Vallisoletano", ["Valladolidian"]], ["Wulfrunian", ["Wolverhamptonian", "Wolverhamptonite"]], ["Novocastrian", ["Newcastleite", "Newcastlite"]], ["Trifluvian", ["Trois-Rivièrester"]], ["Leodenisian", ["Leedsian"]], ["Minneapolitan", ["Minneapolisian"]], ["Hartlepudlian", ["Hartlepoolian"]], ["Liverpudlian", ["Liverpoolian"]], ["Haligonian", ["Halifaxer"]], ["Varsovian", ["Warsawer", "Warsawian"]], ["Providentian", ["Providencian", "Providencer"]], ["Tridentine", ["Trentian", "Trentonian"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/terms/eponymous_adjectives.py0000644000000000000000000000122000000000000021716 0ustar00"""Eponymous adjectives. --- layout: post source: Garner's Modern American Usage source_url: http://bit.ly/1T4alrY title: Eponymous adjectives date: 2014-06-10 12:31:19 categories: writing --- Eponymous adjectives. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "garner.eponymous_adjective" msg = "'{}' is the preferred eponymous adjective." preferences = [ ["Mephistophelean", ["Mephistophelian"]], ["Shakespearean", ["Shakespearian"]], ] return preferred_forms_check(text, preferences, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/terms/venery.py0000644000000000000000000000503700000000000017001 0ustar00"""Names for groups of animals. --- layout: post source: Oxford Dictionaries source_url: http://www.oxforddictionaries.com/words/what-do-you-call-a-group-of title: Names for groups of animals date: 2014-06-10 12:31:19 categories: writing --- Names for groups of animals. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Check the text.""" err = "oxford.venery_terms" msg = "The venery term is '{}'." term_list = [ ["alligators", "congregation"], ["antelopes", "herd"], ["baboons", "troop"], ["badgers", "cete"], ["bats", "colony"], ["bears", "sloth"], ["buffalo", "herd"], ["bullfinches", "bellowing"], ["caribou", "herd"], ["cats", "glaring"], ["caterpillars", "army"], ["cockroaches", "intrusion"], ["coyotes", "pack"], ["crows", "murder"], ["dogs", "pack"], ["eagles", "convocation"], ["emus", "mob"], ["flamingos", "stand"], ["frogs", "army"], ["goldfinches", "charm"], ["gorillas", "band"], ["guineafowl", "rasp"], ["hedgehogs", "array"], ["herons", "siege"], ["hogs", "parcel"], ["hyenas", "cackle"], ["ibex", "herd"], ["iguanas", "mess"], ["lions", "pride"], ["locusts", "plague"], ["mackerel", "shoal"], ["mares", "stud"], ["minnows", "shoal"], ["moose", "herd"], ["mosquitoes", "scourge"], ["nightingales", "watch"], ["oysters", "bed"], ["partridges", "covey"], ["pelicans", "pod"], ["raccoons", "gaze"], ["ravens", "unkindness"], ["rhinoceroses", "crash"], ["sea urchins", "sea"], ["starlings", "murmuration"], ["toads", "knot"], ["wombats", "wisdom"], ["woodcocks", "fall"], ["woodpeckers", "descent"], ["wrens", "herd"], ] generic_terms = [ "group", "bunch", ] list = [] for term_pair in term_list: for generic in generic_terms: wrong = f"a {generic} of {term_pair[0]}" right = f"a {term_pair[1]} of {term_pair[0]}" list += [[right, [wrong]]] return preferred_forms_check(text, list, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/typography/__init__.py0000644000000000000000000000003400000000000020274 0ustar00"""Advice on typography.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/typography/diacritical_marks.py0000644000000000000000000001100500000000000022202 0ustar00"""Diacritical marks. Use of diacritical marks where common. """ from proselint.tools import memoize, preferred_forms_check @memoize def check(text): """Suggest the preferred forms.""" err = "typography.diacritical_marks" msg = "Use diacritical marks in '{}'." list = [ # French loanwords ["beau idéal", ["beau ideal"]], ["boutonnière", ["boutonniere"]], ["bric-à-brac", ["bric-a-brac"]], ["café", ["cafe"]], ["cause célèbre", ["cause celebre"]], ["chèvre", ["chevre"]], ["cliché", ["cliche"]], ["comme ci comme ça", ["comme ci comme ca", "comsi comsa"]], ["consommé", ["consomme"]], ["coup d'état", ["coup d'etat"]], ["coup de grâce", ["coup de grace"]], ["crudités", ["crudites"]], ["crème brûlée", ["creme brulee"]], ["crème de menthe", ["creme de menthe"]], ["crème fraîche", ["creme fraice"]], ["crème fraîche", ["creme fresh"]], ["crêpe", ["crepe"]], ["débutante", ["debutante"]], ["décor", ["decor"]], ["déjà vu", ["deja vu"]], ["dénouement", ["denouement"]], ["façade", ["facade"]], ["fiancé", ["fiance"]], ["fiancée", ["fiancee"]], ["flambé", ["flambe"]], ["garçon", ["garcon"]], ["lycée", ["lycee"]], ["maître d", ["maitre d"]], ["ménage à trois", ["menage a trois"]], ["négligée", ["negligee"]], ["papier-mâché", ["papier-mache", "paper mache", "paper-mache"]], ["protégé", ["protege"]], ["protégée", ["protegee"]], ["purée", ["puree"]], ["raison d'être", ["raison d'etre"]], ["my résumé", ["my resume"]], ["your résumé", ["your resume"]], ["his résumé", ["his resume"]], ["her résumé", ["her resume"]], ["a résumé", ["a resume"]], ["the résumé", ["the resume"]], ["risqué", ["risque"]], ["roué", ["roue"]], ["soirée", ["soiree"]], ["soufflé", ["souffle"]], ["soupçon", ["soupcon"]], ["touché", ["touche"]], ["tête-à-tête", ["tete-a-tete"]], ["voilà", ["voila"]], ["à la carte", ["a la carte"]], ["à la mode", ["a la mode"]], ["émigré", ["emigre"]], # Spanish loanwords ["El Niño", ["El Nino"]], ["jalapeño", ["jalapeno"]], ["La Niña", ["La Nina"]], ["piña colada", ["pina colada"]], ["señor", ["senor"]], ["señora", ["senora"]], ["señorita", ["senorita"]], # Portuguese loanwords ["açaí", ["acai"]], # German loanwords ["doppelgänger", ["doppelganger"]], ["Führer", ["Fuhrer"]], ["Gewürztraminer", ["Gewurztraminer"]], ["vis-à-vis", ["vis-a-vis"]], ["Übermensch", ["Ubermensch"]], # Swedish loanwords ["filmjölk", ["filmjolk"]], ["smörgåsbord", ["smorgasbord"]], # Names, places, and companies ["Beyoncé", ["Beyonce"]], ["Brontë", ["Bronte"]], ["Brontë", ["Bronte"]], ["Champs-Élysées", ["Champs-Elysees"]], ["Citroën", ["Citroen"]], ["Curaçao", ["Curacao"]], ["Häagen-Dazs", ["Haagen-Dazs", "Haagen Dazs"]], ["Löwenbräu", ["Lowenbrau"]], ["Monégasque", ["Monegasque"]], ["Mötley Crüe", ["Motley Crue"]], ["Nescafé", ["Nescafe"]], ["Queensrÿche", ["Queensryche"]], ["Québec", ["Quebec"]], ["Québécois", ["Quebecois"]], ["Ångström", ["Angstrom"]], ["ångström", ["angstrom"]], ["Škoda", ["Skoda"]], ] return preferred_forms_check(text, list, err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/typography/exclamation.py0000644000000000000000000000167000000000000021050 0ustar00"""Too much yelling. --- layout: post source: ??? source_url: ??? title: yelling date: 2014-06-10 12:31:19 categories: writing --- Too much yelling. """ from proselint.tools import existence_check, max_errors, memoize, ppm_threshold @max_errors(1) @memoize def check_repeated_exclamations(text): """Check the text.""" err = "leonard.exclamation.multiple" msg = "Stop yelling. Keep your exclamation points under control." regex = r"[\!]\s*?[\!]{1,}" return existence_check(text, [regex], err, msg, require_padding=False, ignore_case=False, dotall=True) @ppm_threshold(30) @memoize def check_exclamations_ppm(text): """Make sure that the exclamation ppm is under 30.""" err = "leonard.exclamation.30ppm" msg = "More than 30 ppm of exclamations. Keep them under control." regex = r"\w!" return existence_check(text, [regex], err, msg, require_padding=False) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/typography/symbols.py0000644000000000000000000000526500000000000020240 0ustar00"""Use the right symbols. source: Butterick's Practical Typography source_url: http://practicaltypography.com/ """ from proselint.tools import existence_check, max_errors, memoize @max_errors(3) @memoize def check_ellipsis(text): """Use an ellipsis instead of three dots.""" err = "typography.symbols.ellipsis" msg = "'...' is an approximation, use the ellipsis symbol '…'." regex = r"\.\.\." return existence_check(text, [regex], err, msg, require_padding=False, offset=0) @max_errors(1) @memoize def check_copyright_symbol(text): """Use the copyright symbol instead of (c).""" err = "typography.symbols.copyright" msg = "(c) is a goofy alphabetic approximation, use the symbol ©." regex = r"\([cC]\)" return existence_check(text, [regex], err, msg, require_padding=False) @max_errors(3) @memoize def check_trademark_symbol(text): """Use the trademark symbol instead of (TM).""" err = "typography.symbols.trademark" msg = "(TM) is a goofy alphabetic approximation, use the symbol ™." regex = r"\(TM\)" return existence_check(text, [regex], err, msg, require_padding=False) @max_errors(3) @memoize def check_registered_trademark_symbol(text): """Use the registered trademark symbol instead of (R).""" err = "typography.symbols.trademark" msg = "(R) is a goofy alphabetic approximation, use the symbol ®." regex = r"\([rR]\)" return existence_check(text, [regex], err, msg, require_padding=False) @max_errors(3) @memoize def check_sentence_spacing(text): """Use no more than two spaces after a period.""" err = "typography.symbols.sentence_spacing" msg = "More than two spaces after the period; use 1 or 2." regex = r"\. {3}" return existence_check(text, [regex], err, msg, require_padding=False) @max_errors(3) @memoize def check_multiplication_symbol(text): """Use the multiplication symbol ×, not the lowercase letter x.""" err = "typography.symbols.multiplication_symbol" msg = "Use the multiplication symbol ×, not the letter x." regex = r"[0-9]+ ?x ?[0-9]+" return existence_check(text, [regex], err, msg, require_padding=False) @max_errors(3) @memoize def check_curly_quotes(text): """Use curly quotes, not straight quotes.""" err = "typography.symbols.curly_quotes" msg = 'Use curly quotes “”, not straight quotes "".' regex = r"\"[\w\s\d]+\"" return existence_check(text, [regex], err, msg, require_padding=False) # @memoize # def check_en_dash_separated_names(text): # """Use an en-dash to separate names.""" # # [u"[A-Z][a-z]{1,10}[-\u2014][A-Z][a-z]{1,10}", # # u"Use an en dash (–) to separate names."], ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/uncomparables/__init__.py0000644000000000000000000000004100000000000020717 0ustar00"""Comparing an uncomparable.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/uncomparables/misc.py0000644000000000000000000001006200000000000020117 0ustar00"""Comparing uncomparables. --- layout: post source: David Foster Wallace source_url: http://www.telegraph.co.uk/a/9715551 title: Comparing an uncomparable date: 2014-06-10 12:31:19 categories: writing --- David Foster Wallace says: > This is one of a class of adjectives, sometimes called “uncomparables”, that can be a little tricky. Among other uncomparables are precise, exact, correct, entire, accurate, preferable, inevitable, possible, false; there are probably two dozen in all. These adjectives all describe absolute, non-negotiable states: something is either false or it’s not; something is either inevitable or it’s not. Many writers get careless and try to modify uncomparables with comparatives like more and less or intensives like very. But if you really think about them, the core assertions in sentences like “War is becoming increasingly inevitable as Middle East tensions rise”; “Their cost estimate was more accurate than the other firms’”; and “As a mortician, he has a very unique attitude” are nonsense. If something is inevitable, it is bound to happen; it cannot be bound to happen and then somehow even more bound to happen. Unique already means one-of-a-kind, so the adj. phrase very unique is at best redundant and at worst stupid, like “audible to the ear” or “rectangular in shape”. You can blame the culture of marketing for some of this difficulty. As the number and rhetorical volume of US ads increase, we become inured to hyperbolic language, which then forces marketers to load superlatives and uncomparables with high-octane modifiers (special - very special - Super-special! - Mega-Special!!), and so on. A deeper issue implicit in the problem of uncomparables is the dissimilarities between Standard Written English and the language of advertising. Advertising English, which probably deserves to be studied as its own dialect, operates under different syntactic rules than SWE, mainly because AE’s goals and assumptions are different. Sentences like “We offer a totally unique dining experience”; “Come on down and receive your free gift”; and “Save up to 50 per cent… and more!” are perfectly OK in Advertising English — but this is because Advertising English is aimed at people who are not paying close attention. If your audience is by definition involuntary, distracted and numbed, then free gift and totally unique stand a better chance of penetrating — and simple penetration is what AE is all about. One axiom of Standard Written English is that your reader is paying close attention and expects you to have done the same. """ import itertools from proselint.tools import existence_check, memoize @memoize def check(text): """Check the text.""" err = "uncomparables.misc" msg = "Comparison of an uncomparable: '{}' is not comparable." comparators = [ "most", "more", "less", "least", "very", "quite", "largely", "extremely", "increasingly", "kind of", "mildly" ] uncomparables = [ "absolute", "adequate", "chief", "complete", "correct", "devoid", "entire", "false", "fatal", "favorite", "final", "ideal", "impossible", "inevitable", "infinite", "irrevocable", "main", "manifest", "only", "paramount", "perfect", "perpetual", "possible", "preferable", "principal", "singular", "stationary", "sufficient", "true", "unanimous", "unavoidable", "unbroken", "uniform", "unique", "universal", "void", "whole", ] exceptions = [ ("more", "perfect"), ("more", "possible") # FIXME ] uncomparables = [fr"{i[0]}\s{i[1]}" for i in itertools.product( comparators, uncomparables) if i not in exceptions] return existence_check(text, uncomparables, err, msg, require_padding=True) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/weasel_words/__init__.py0000644000000000000000000000002400000000000020563 0ustar00"""Weasel words.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/weasel_words/misc.py0000644000000000000000000000063300000000000017765 0ustar00"""Weasel words. --- layout: post source: write-good source_url: https://github.com/btford/write-good title: Weasel words. date: 2014-06-10 12:31:19 categories: writing --- Weasel words clearly weaken various aspects of a number of your sentences. """ # def check(text): # error_code = "weasel_words.misc" # msg = "Weasel words present." # return [(1, 1, error_code, msg)] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/checks/weasel_words/very.py0000644000000000000000000000132200000000000020013 0ustar00"""Very. --- layout: post source: William Allen White source_url: http://bit.ly/1XaMllw title: very date: 2014-06-10 12:31:19 categories: writing --- Substitute 'damn' every time you're inclined to write 'very'; your editor will delete it and the writing will be just as it should be. """ from proselint.tools import existence_check, max_errors, memoize @max_errors(1) @memoize def check(text): """Avoid 'very'.""" err = "weasel_words.very" msg = ("Substitute 'damn' every time you're " "inclined to write 'very'; your editor will delete it " "and the writing will be just as it should be.") regex = "very" return existence_check(text, [regex], err, msg) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/command_line.py0000644000000000000000000001315600000000000015525 0ustar00"""Command line utility for proselint.""" import json import os import shutil import subprocess import sys import traceback import click from .config import default from .tools import (close_cache_shelves, close_cache_shelves_after, errors_to_json, lint, load_options) from .version import __version__ CONTEXT_SETTINGS = {"help_option_names": ['-h', '--help']} base_url = "proselint.com/" proselint_path = os.path.dirname(os.path.realpath(__file__)) demo_file = os.path.join(proselint_path, "demo.md") # TODO: fix broken corpus def timing_test(corpus="0.1.0"): """Measure timing performance on the named corpus.""" import time dirname = os.path.dirname corpus_path = os.path.join( dirname(dirname(os.path.realpath(__file__))), "corpora", corpus) start = time.time() for file in os.listdir(corpus_path): filepath = os.path.join(corpus_path, file) if ".md" == filepath[-3:]: subprocess.call(["proselint", filepath, ">/dev/null"]) return time.time() - start def clear_cache(): """Delete the contents of the cache.""" click.echo("Deleting the cache...") # see issue #624 _delete_compiled_python_files() _delete_cache() def _delete_compiled_python_files(): """Remove files with a 'pyc' extension.""" for path, _, files in os.walk(os.getcwd()): for fname in [f for f in files if os.path.splitext(f)[1] == ".pyc"]: try: os.remove(os.path.join(path, fname)) except OSError: pass def _delete_cache(): """Remove the proselint cache.""" proselint_cache = os.path.join("proselint", "cache") try: shutil.rmtree(proselint_cache) except OSError: pass def print_errors(filename, errors, output_json=False, compact=False): """Print the errors, resulting from lint, for filename.""" if output_json: click.echo(errors_to_json(errors)) else: for error in errors: (check, message, line, column, start, end, extent, severity, replacements) = error if compact: filename = "-" click.echo( filename + ":" + str(1 + line) + ":" + str(1 + column) + ": " + check + " " + message) @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, help="Output as JSON.") @click.option('--time', '-t', is_flag=True, help="Time on a corpus.") @click.option('--demo', is_flag=True, help="Run over demo file.") @click.option('--compact', is_flag=True, help="Shorten output.") @click.option('--dump-config', is_flag=True, help="Prints current config.") @click.option('--dump-default-config', is_flag=True, help="Prints default config.") @click.argument('paths', nargs=-1, type=click.Path()) @close_cache_shelves_after def proselint(paths=None, config=None, version=None, clean=None, debug=None, output_json=None, time=None, demo=None, compact=None, dump_config=None, dump_default_config=None): """Create the CLI for proselint, a linter for prose.""" if dump_default_config: return print(json.dumps(default, sort_keys=True, indent=4)) config = load_options(config, default) if dump_config: print(json.dumps(config, sort_keys=True, indent=4)) return if time: # click.echo(timing_test()) print("This option does not work for the time being.") return # In debug or clean mode, delete cache & *.pyc files before running. if debug or clean: clear_cache() # Use the demo file by default. if demo: paths = [demo_file] # Expand the list of directories and files. filepaths = extract_files(list(paths)) # Lint the files num_errors = 0 # Use stdin if no paths were specified if len(paths) == 0: filepaths.append('-') for fp in filepaths: if fp == '-': fp = '' f = sys.stdin else: try: f = click.open_file(fp, 'r', "utf-8", "replace") except Exception: traceback.print_exc() sys.exit(2) errors = lint(f, debug, config) num_errors += len(errors) print_errors(fp, errors, output_json, compact) # Return an exit code close_cache_shelves() if num_errors > 0: sys.exit(1) else: sys.exit(0) def extract_files(files): """Expand list of paths to include all text files matching the pattern.""" expanded_files = [] legal_extensions = [".md", ".txt", ".rtf", ".html", ".tex", ".markdown"] for f in files: # If it's a directory, recursively walk through it and find the files. if os.path.isdir(f): for dir_, _, filenames in os.walk(f): for filename in filenames: fn, file_extension = os.path.splitext(filename) if file_extension in legal_extensions: joined_file = os.path.join(dir_, filename) expanded_files.append(joined_file) # Otherwise add the file directly. else: expanded_files.append(f) return expanded_files if __name__ == '__main__': proselint() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/config.py0000644000000000000000000000550700000000000014346 0ustar00"""Proselint config - replacement for default .proselintrc since #1212.""" default = { "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": True, "weasel_words.misc": True, "weasel_words.very": True } } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/demo.md0000644000000000000000000001371700000000000013777 0ustar00# Proselint Writing is notoriously hard, even for the best writers. Yet there is a tremendous amount of knowledge about the discipline strewn across usage guides, dictionaries, technical manuals, essays, pamphlets, websites, and the hearts and minds of great authors and editors. But poring over Strunk & White hardly makes one a better writer — it turns you into neither Strunk nor White. And nobody has the capacity to apply all the advice from Garner’s Modern American Usage, a 975-page usage guide, to everything they write. In fact, the whole notion that one becomes a better writer by reading advice on writing rests on untenable assumptions about learning and memory. The traditional formats of knowledge about writing are thus essentially inert, waiting to be transformed. We devised a simple solution: proselint, a linter for prose. (A linter is a computer program that, like a spell checker, scans through a document and analyzes it.) Proselint places the world's greatest writers and editors by your side, where they whisper suggestions on how to improve your prose. You'll be guided by advice inspired by Bryan Garner, David Foster Wallace, Chuck Palahniuk, Steve Pinker, Mary Norris, Mark Twain, Elmore Leonard, George Orwell, Matthew Butterick, William Strunk, E.B. White, Philip Corbett, Ernest Gowers, and the editorial staff of the world's finest literary magazines and newspapers, among others. Our goal is to aggregate knowledge about best practices in writing and to make that knowledge immediately accessible to all authors in the form of a linter for prose. This is a live demo. Obviously, you can hover over the underlined text to see what the issue is, then fix it. # Dates & times The 1950's were a swell time. The 50's were a swell time. Things happened from 1980-1999 and from 240-398 A.D. March, 2013 was notable in that In February of 2010, the mayor considered It's 5 pm somewhere. It's 12 a.m., time to eat lunch. It's 11 p.m. at night. # Consistency This is a sentence! One space after a period. This is a sentence. Two spaces after a period. This is a sentence? Two spaces after a period. This is a sentence. One space after a period. This is a sentence. One space after a period. This is a sentence. One space after a period. This is a sentence. One space after a period. centre centre center organize, organized, organizing, organise recognize, recognise, recognise, recognise # Symbols $1000 USD I hit him over the head with a 2 x 4. # Venery terms A bunch of antelopes walked by the road. A group of emus attacked me. She swam by a bunch of oysters. # Mondegreens They hae slain the Earl o' Moray and Lady Mondegreen. A girl with colitis goes by. # Skunked terms The building is deceptively large. The project would decimate the fragile wetland wilderness. Hopefully, one day we will all grow older. # Links www.google.com http://broken.proselint.com http://news.ycombinator.com # Hyperbolic language and so I said PLEASE STOP YELLING so excited! so excited!! so excited!!! so excited!!!! really?? and so I said PLEASE STOP YELLING and so I said PLEASE STOP YELLING okay? THESE ARE SMALL CAPS at the beginning of a new line. # Preferred forms abbreviatable to NASA academicly superior. transhipped an aider and abbeter it's adducable let's look for an acquiror i wonder what tort-feasor means Get that off of me before I catch on fire! There are many a person I have met and worked with who simply deride themselves into taking some action In the meanwhile, he looked loving at the sheep. Suddenly, I see. # Mixed metaphors Get ready: button your seatbelts. The cream rises to the crop. The biggest bottleneck is that... # Illogic and redundancy he is very smart approximately about 5 atm machines atm machine we associate together it's blatantly obvious that this obviously works. a very unique idea a more perfect union the surrounding circumstances he filled a much-needed gap To coin a phrase from the movie, # Blowing the punchline Suddenly, the car came to a stop. All hell broke loose on the farm. # Clichés under the weather He's a chip off the old block a quantum leap Our cutting edge decision-making process will make your life crystal clear. He's a thought leader. # Security John's cc#: 378282246310005 the password is tnoernturpn my password is long and 'long' my password is amazing # Commercialese inst. please be advised that # Archaism boughten # Dismissive tone this obviously works # Chatspeak brb rofl # Ogilvy's pretension We'll need to reconceptualize this sentence. # Airlinese enplanement We'll be taking off momentarily. # Tense present Save up to 50% or More! between you and i I did it on accident I feel nauseous # Phrasal adjectives It was a highly-anticipated event. The English speaking people speak English. A big ticket item. A right wing militia. # Various misspellings highfaluting the statement was inadmissable in court Nikita Khruschev I feel innundated with email Nicknack He's a shoe-in Brett Farve and Dwayne Wade are good friends. The Chronicals of Narnia # Strunk & White composition & regex checks I did not pay for the check. Honestly, attention to detail is useful. I did not pay attention to detail. I did not pay any attention to detail. # Typography (c) 2015 (R) The Corporation Use ellipsis not three dots... # Denizen labels The Manchesterian was a good Brit. One from Michigan is not a Michiganite but a Michigander. One from Colorado is not a Coloradoan but a Coloradan. # Sexism The lady lawyer handled my case. John and Mary married. Now they are man and wife. Chairman Mao was the chairman of the communist party. # Punctuation Smith, et. al (2006) said # Quoted text John said that I am "very unique." John knows that I am very unique. John knows every unique snowflake is cold. # Metadiscourse The preceeding discussion # Narcisism, metadiscourse, latin, and hedging The 'take-home message' is that more research is needed The rest of this article argues that, to a certain degree in recent years, an increasing number of psychologists have mutatis mutandis ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/score.py0000644000000000000000000000472600000000000014216 0ustar00"""Compute the lintscore on the corpus.""" import os import re import subprocess proselint_path = os.path.dirname(os.path.realpath(__file__)) def score(check=None): """Compute the linter's score on the corpus. Proselint's score reflects the desire to have a linter that catches many errors, but which takes false alarms seriously. It is better not to say something than to say the wrong thing, and the harm from saying the wrong thing is greater than the benefit of saying the right thing. Thus our score metric is defined as: TP * (TP / (FP + TP)) ^ k, where TP is the number of true positives (hits), FP is the number of false positives (false alarms), and k > 0 is a temperature parameter that determines the penalty for imprecision. In general, we should choose a large value of k, one that strongly discourages the creation of rules that can't be trusted. Suppose that k = 2. Then if the linter detects 100 errors, of which 10 are false positives, the score is 81. """ tp = 0 fp = 0 parent_directory = os.path.dirname(proselint_path) path_to_corpus = os.path.join(parent_directory, "corpora", "0.1.0") for root, _, files in os.walk(path_to_corpus): files = [f for f in files if f.endswith(".md")] for f in files: fullpath = os.path.join(root, f) # Run the linter. print(f"Linting {f}") out = subprocess.check_output(["proselint", fullpath]) # Determine the number of errors. regex = r".+?:(?P\d+):(?P\d+): (?P.+)" num_errors = len(tuple(re.finditer(regex, out))) print(f"Found {num_errors} errors.") # Open the document. subprocess.call(["open", fullpath]) # Ask the scorer how many of the errors were false alarms? input_val = None while not isinstance(input_val, int): try: input_val = input("# of false alarms? ") if input_val == "exit": return else: input_val = int(input_val) fp += input_val tp += (num_errors - input_val) except ValueError: pass print(f"Currently {tp} hits and {fp} false alarms\n---") if (tp + fp) > 0: return tp * (1.0 * tp / (tp + fp)) ** 2 else: return 0 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/tools.py0000644000000000000000000003354000000000000014237 0ustar00"""General-purpose tools shared across linting checks.""" import copy import dbm import functools import hashlib import importlib import inspect import json import os import re import shelve import sys import traceback from warnings import showwarning as warn _cache_shelves = dict() proselint_path = os.path.dirname(os.path.realpath(__file__)) home_dir = os.path.expanduser("~") cwd = os.getcwd() def close_cache_shelves(): """Close previously opened cache shelves.""" for cache in _cache_shelves.values(): cache.close() _cache_shelves.clear() def close_cache_shelves_after(f): """Decorate a function to ensure cache shelves are closed after call.""" @functools.wraps(f) def wrapped(*args, **kwargs): f(*args, **kwargs) close_cache_shelves() return wrapped def _get_xdg_path(variable_name, default_path): path = os.environ.get(variable_name) if path is None or path == '': return default_path else: return path def _get_xdg_config_home(): 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(home_dir, '.cache')) def _get_cache(cachepath): if cachepath in _cache_shelves: return _cache_shelves[cachepath] try: cache = shelve.open(cachepath, protocol=2) except dbm.error: # dbm error on open - delete and retry print('Error (%s) opening %s - will attempt to delete and re-open.' % (sys.exc_info()[1], cachepath)) try: os.remove(cachepath) cache = shelve.open(cachepath, protocol=2) except Exception: print('Error on re-open: %s' % sys.exc_info()[1]) cache = None except Exception: # unknown error print('Could not open cache file %s, maybe name collision. ' 'Error: %s' % (cachepath, traceback.format_exc())) cache = None # Don't fail on bad caches if cache is None: print('Using in-memory shelf for cache file %s' % cachepath) cache = shelve.Shelf(dict()) _cache_shelves[cachepath] = cache return cache 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(home_dir, ".proselint") if not os.path.isdir(cache_dirname): # 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. else: os.makedirs(cache_dirname) cache_filename = f.__module__ + "." + f.__name__ cachepath = os.path.join(cache_dirname, cache_filename) @functools.wraps(f) def wrapped(*args, **kwargs): # handle instance methods if hasattr(f, '__self__'): args = args[1:] signature = cache_filename.encode("utf-8") tempargdict = inspect.getcallargs(f, *args, **kwargs) for item in list(tempargdict.items()): if item[0] == "text": signature += item[1].encode("utf-8") key = hashlib.sha256(signature).hexdigest() cache = _get_cache(cachepath) try: return cache[key] except KeyError: value = f(*args, **kwargs) cache[key] = value cache.sync() return value except TypeError: call_to = f.__module__ + '.' + f.__name__ print('Warning: could not disk cache call to %s;' 'it probably has unhashable args. Error: %s' % (call_to, traceback.format_exc())) return f(*args, **kwargs) return wrapped def get_checks(options): """Extract the checks.""" sys.path.append(proselint_path) checks = [] check_names = [key for (key, val) in options["checks"].items() if val] for check_name in check_names: module = importlib.import_module("checks." + check_name) for d in dir(module): if re.match("check", d): checks.append(getattr(module, d)) return checks def deepmerge_dicts(dict1, dict2): """Deep merge dictionaries, second dict will take priority.""" result = copy.deepcopy(dict1) for key, value in dict2.items(): if isinstance(value, dict): result[key] = deepmerge_dicts(result[key] or {}, value) else: result[key] = value return result def load_options(config_file_path=None, conf_default=None): """Read various proselintrc files, allowing user overrides.""" conf_default = conf_default or {} if os.path.isfile("/etc/proselintrc"): conf_default = json.load(open("/etc/proselintrc")) user_config_paths = [ os.path.join(cwd, '.proselintrc.json'), os.path.join(_get_xdg_config_home(), 'proselint', 'config.json'), os.path.join(home_dir, '.proselintrc.json') ] if config_file_path: if not os.path.isfile(config_file_path): raise FileNotFoundError( f"Config file {config_file_path} does not exist") user_config_paths.insert(0, config_file_path) user_options = {} for path in user_config_paths: if os.path.isfile(path): user_options = json.load(open(path)) break oldpath = path.replace(".json", "") if os.path.isfile(oldpath): warn(f"{oldpath} was found instead of a JSON file." f" Rename to {path}.", DeprecationWarning, "", 0) user_options = json.load(open(oldpath)) break return deepmerge_dicts(conf_default, user_options) def errors_to_json(errors): """Convert the errors to JSON.""" out = [] for e in errors: out.append({ "check": e[0], "message": e[1], "line": 1 + e[2], "column": 1 + e[3], "start": 1 + e[4], "end": 1 + e[5], "extent": e[6], "severity": e[7], "replacements": e[8], }) return json.dumps( {"status": "success", "data": {"errors": out}}, sort_keys=True) def line_and_column(text, position): """Return the line number and column of a position in a string.""" position_counter = 0 line_no = 0 for line in text.splitlines(True): if (position_counter + len(line.rstrip())) >= position: break position_counter += len(line) line_no += 1 return (line_no, position - position_counter) def lint(input_file, debug=False, config=None): """Run the linter on the input file.""" config = config or {} if isinstance(input_file, str): text = input_file else: text = input_file.read() # Get the checks. checks = get_checks(config) # Apply all the checks. errors = [] for check in checks: result = check(text) for error in result: (start, end, check, message, replacements) = error (line, column) = line_and_column(text, start) if not is_quoted(start, text): errors += [(check, message, line, column, start, end, end - start, "warning", replacements)] if len(errors) > config["max_errors"]: break # Sort the errors by line and column number. errors = sorted(errors[:config["max_errors"]], key=lambda e: (e[2], e[3])) return errors def assert_error(text, check, n=1): """Assert that text has n errors of type check.""" assert_error.description = f"No {check} error for '{text}'" assert(check in [error[0] for error in lint(text)]) def consistency_check(text, word_pairs, err, msg, offset=0): """Build a consistency checker for the given word_pairs.""" errors = [] msg = " ".join(msg.split()) for w in word_pairs: matches = [ [m for m in re.finditer(w[0], text)], [m for m in re.finditer(w[1], text)] ] if len(matches[0]) > 0 and len(matches[1]) > 0: idx_minority = len(matches[0]) > len(matches[1]) for m in matches[idx_minority]: errors.append(( m.start() + offset, m.end() + offset, err, msg.format(w[~idx_minority], m.group(0)), w[~idx_minority])) 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: flags = re.IGNORECASE else: flags = 0 msg = " ".join(msg.split()) errors = [] regex = r"[\W^]{}[\W$]" for p in list: for r in p[1]: for m in re.finditer(regex.format(r), text, flags=flags): txt = m.group(0).strip() errors.append(( m.start() + 1 + offset, m.end() + offset, err, msg.format(p[0], txt), p[0])) return errors def existence_check(text, list, err, msg, ignore_case=True, str=False, offset=0, require_padding=True, dotall=False, excluded_topics=None, exceptions=(), join=False): """Build a checker that prohibits certain words or phrases.""" flags = 0 msg = " ".join(msg.split()) if ignore_case: flags = flags | re.IGNORECASE if str: flags = flags | re.UNICODE if dotall: flags = flags | re.DOTALL if require_padding: regex = r"(?:^|\W){}[\W$]" else: regex = r"{}" errors = [] # If the topic of the text is in the excluded list, return immediately. if excluded_topics: tps = topics(text) if any([t in excluded_topics for t in tps]): return errors rx = "|".join(regex.format(w) for w in list) for m in re.finditer(rx, text, flags=flags): txt = m.group(0).strip() if any([re.search(exception, txt) for exception in exceptions]): continue errors.append(( m.start() + 1 + offset, m.end() + offset, err, msg.format(txt), None)) return errors def max_errors(limit): """Decorate a check to truncate error output to a specified limit.""" def wrapper(f): @functools.wraps(f) def wrapped(*args, **kwargs): return truncate_errors(f(*args, **kwargs), limit) return wrapped return wrapper def truncate_errors(errors, limit=float("inf")): """If limit was specified, truncate the list of errors. Give the total number of times that the error was found elsewhere. """ if len(errors) > limit: start1, end1, err1, msg1, replacements = errors[0] if len(errors) == limit + 1: msg1 += " Found once elsewhere." else: msg1 += f" Found {len(errors)} times elsewhere." errors = [(start1, end1, err1, msg1, replacements)] + errors[1:limit] return errors def ppm_threshold(threshold): """Decorate a check to error if the PPM threshold is surpassed.""" def wrapped(f): @functools.wraps(f) def wrapper(*args, **kwargs): return threshold_check(f(*args, **kwargs), threshold, len(args[0])) return wrapper return wrapped def threshold_check(errors, threshold, length): """Check that returns an error if the PPM threshold is surpassed.""" if length > 0: errcount = len(errors) ppm = (errcount / length) * 1e6 if ppm >= threshold and errcount >= 1: return [errors[0]] return [] def is_quoted(position, text): """Determine if the position in the text falls within a quote.""" def matching(quotemark1, quotemark2): straight = '\"\'' curly = '“”' if quotemark1 in straight and quotemark2 in straight: return True if quotemark1 in curly and quotemark2 in curly: return True else: return False def find_ranges(text): s = 0 q = pc = '' start = None ranges = [] seps = " .,:;-\r\n" quotes = ['\"', '“', '”', "'"] for i, c in enumerate(text + "\n"): if s == 0 and c in quotes and pc in seps: start = i s = 1 q = c elif s == 1 and matching(c, q): s = 2 elif s == 2: if c in seps: ranges.append((start+1, i-1)) start = None s = 0 else: s = 1 pc = c return ranges def position_in_ranges(ranges, position): for start, end in ranges: if start <= position < end: return True return False return position_in_ranges(find_ranges(text), position) def detector_50_Cent(text): """Determine whether 50 Cent is a topic.""" keywords = [ "50 Cent", "rap", "hip hop", "Curtis James Jackson III", "Curtis Jackson", "Eminem", "Dre", "Get Rich or Die Tryin'", "G-Unit", "Street King Immortal", "In da Club", "Interscope", ] num_keywords = sum(word in text for word in keywords) return ("50 Cent", float(num_keywords > 2)) def topics(text): """Return a list of topics.""" detectors = [ detector_50_Cent ] ts = [] for detector in detectors: ts.append(detector(text)) return [t[0] for t in ts if t[1] > 0.95] def context(text, position, level="paragraph"): """Get sentence or paragraph that surrounds the given position.""" if level == "sentence": pass elif level == "paragraph": pass return "" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/proselint/version.py0000644000000000000000000000007000000000000014554 0ustar00"""Proselint version number.""" __version__ = "0.13.0" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/pyproject.toml0000644000000000000000000000225200000000000013416 0ustar00[tool.poetry] name = "proselint" version = "0.13.0" description = "A linter for prose." license = "BSD-3-Clause" authors = ["Amperser Labs "] readme = "README.md" homepage = "https://proselint.com" repository = "https://github.com/amperser/proselint" classifiers = ["Programming Language :: Python :: Implementation :: CPython"] include = [ "proselint/", "tests/", "proselint/demo.md", ] exclude = ["tests/.gitignore"] [tool.poetry.dependencies] python = "^3.6.1" click = "^8.0.0" future = "^0.18.2" six = "^1.15.0" [tool.poetry.dev-dependencies] gmail = {git = "https://github.com/charlierguo/gmail.git"} APScheduler = ">=3.5.3" bumpversion = ">=0.5.3" coverage = "^6.1" Flask-API = ">=1.0" Flask-Cors = ">=3.0.4" Flask = ">=1.1.4" Flask-Limiter = ">=1.0.1" gunicorn = ">=19.8.1" mock = ">=2.0.0" pytest = "^6.2.5" redis = ">=2.10.6" requests = ">=2.19.1" rq = ">=0.12.0" pydocstyle = "^6.1.1" twine = "^3.5.0" flake8 = "^4.0.1" flake8-bugbear = "^21.9.2" flake8-import-order = "^0.18.1" isort = "^5.10.0" [tool.poetry.scripts] proselint = "proselint.command_line:proselint" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/__init__.py0000644000000000000000000000003300000000000013750 0ustar00"""Tests for proselint.""" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/_test_version.py0000644000000000000000000000073600000000000015106 0ustar00"""Test version number.""" from click.testing import CliRunner from proselint.command_line import proselint from proselint.version import __version__ from .check import Check class TestCheck(Check): """Test class for version number.""" __test__ = True def test_version(self): """Make sure the version number is correct.""" runner = CliRunner() output = runner.invoke(proselint, "--version") assert __version__ in output.stdout ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/check.py0000644000000000000000000000326100000000000013274 0ustar00"""Check that a check is working.""" import codecs import os from unittest import TestCase class Check(TestCase): """All tests inherit from Check.""" __test__ = False def setUp(self): """Create a placeholder for setup procedure.""" pass def tearDown(self): """Create a placeholder for teardown procedure.""" pass @property def this_check(self): """Create a placeholder for the specific check.""" raise NotImplementedError def passes(self, lst): """Check if the test runs cleanly on the given text.""" if isinstance(lst, str): lst = [lst] errors = [] for text in lst: errors += self.this_check.check.__wrapped__(text) return len(errors) == 0 def wpe_too_high(self): """Check whether the check is too noisy.""" min_wpe = 50 examples_dir = os.path.join(os.getcwd(), "tests", "corpus") examples = os.listdir(examples_dir) for example in examples: example_path = os.path.join(examples_dir, example) if ".DS_Store" in example_path: break # Compute the number of words per (wpe) error. with codecs.open(example_path, "r", encoding='utf-8') as f: text = f.read() num_errors = len(self.this_check.check.__wrapped__(text)) num_words = len(text) try: wpe = 1.0 * num_words / num_errors except ZeroDivisionError: wpe = float('Inf') # Make sure that assert wpe > min_wpe, \ f"{example} has only {round(wpe, 2)} wpe." ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/illegal-chars.txt0000644000000000000000000000010100000000000015103 0ustar00This file contains an illegal character on the next line... don ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/test_annotations.py0000644000000000000000000000107500000000000015614 0ustar00"""Tests for annotations.misc check.""" from proselint.checks.annotations import misc as chk from .check import Check class TestCheck(Check): """The test class for annotations.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for annotations.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert self.passes("""Add it to the to do list.""") assert not self.passes("""Add it to the TODO list.""") ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/test_archaism.py0000644000000000000000000000110500000000000015040 0ustar00"""Tests for archaism.misc check.""" from proselint.checks.archaism import misc as chk from .check import Check class TestCheck(Check): """The test class for archaism.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for archaism.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert self.passes("""I want to sleep, and maybe dream.""") assert not self.passes("""I want to sleep, perchance to dream.""") ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/test_butterick_symbols.py0000644000000000000000000000306200000000000017021 0ustar00"""Test Butterick's symbols.""" from proselint.checks.typography import symbols as chk from .check import Check class TestCheck(Check): """The test class for typography.symbols.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_ellipsis(self): """Find ... in a string.""" assert chk.check_ellipsis("""The long and winding road...""") def test_copyright(self): """Find a (c) or (C) in a string.""" assert chk.check_copyright_symbol("""Show me the money! (C)""") assert chk.check_copyright_symbol("""Show me the money! (c)""") def test_trademark(self): """Find a (TM) or (tm) in a string.""" assert chk.check_trademark_symbol("""The Fresh Maker (TM)""") assert chk.check_trademark_symbol("""The Fresh Maker (tm)""") def test_registered_trademark(self): """Find a (r) or (R) in a string.""" assert chk.check_registered_trademark_symbol("""Just Do It (R)""") assert chk.check_registered_trademark_symbol("""Just Do It (r)""") def test_sentence_spacing(self): """Find a sentence followed by three or more spaces.""" assert chk.check_sentence_spacing( """This is a sentence. This is another.""") def test_multiplication(self): """Find an x between two digits.""" assert chk.check_multiplication_symbol( """It is obvious that 2 x 2 = 4.""") def test_curly_quotes(self): # FIXME """Find "" quotes in a string.""" pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/test_clear_cache.py0000644000000000000000000001066000000000000015470 0ustar00"""Tests for the clear cache operation from proselint.command_line.""" import os import unittest from proselint import command_line as cl try: from unittest import mock except ImportError: # Py2.x from unittest import mock try: from builtins import PermissionError except ImportError: class PermissionError(OSError): """Introduced in Py3.3, emulate for earlier versions.""" def __init__(self, *args, **kwargs): """Constructor.""" OSError.__init__(self, *args, **kwargs) try: from builtins import FileNotFoundError except ImportError: class FileNotFoundError(OSError): """Introduced in Py3.3, emulate for earlier versions.""" def __init__(self, *args, **kwargs): """Constructor.""" OSError.__init__(self, *args, **kwargs) try: from builtins import IsADirectoryError except ImportError: class IsADirectoryError(OSError): """Introduced in Py3.3, emulate for earlier versions.""" def __init__(self, *args, **kwargs): """Constructor.""" OSError.__init__(self, *args, **kwargs) class Test__delete_compiled_python_files(unittest.TestCase): """proselint.command_line._delete_compiled_python_files().""" def setUp(self): """init common data.""" self.base_dir = '.' self.python_file = 'a.py' self.pyc_file = 'a.pyc' self.dot_pyc = '.pyc' self.files = [ (self.base_dir, ('dummy',), (self.pyc_file, self.python_file, self.dot_pyc)) ] self.pyc_file_path = os.path.join(self.base_dir, self.pyc_file) self.python_file_path = os.path.join(self.base_dir, self.python_file) self.dot_pyc_path = os.path.join(self.base_dir, self.python_file) @mock.patch('os.walk') @mock.patch('os.remove') def test_delete_pyc_file(self, mock_remove, mock_walk): """Ensure 'pyc' files are removed.""" mock_walk.return_value = self.files cl._delete_compiled_python_files() mock_remove.assert_called_with(self.pyc_file_path) @mock.patch('os.walk') @mock.patch('os.remove') def test_files_not_deleted(self, mock_remove, mock_walk): """Ensure non 'pyc' files are not removed.""" mock_walk.return_value = self.files cl._delete_compiled_python_files() with self.assertRaises(AssertionError): mock_remove.assert_called_with(self.python_file_path) with self.assertRaises(AssertionError): mock_remove.assert_called_with(self.dot_pyc_path) @mock.patch('os.walk') @mock.patch('os.remove', side_effect=PermissionError) def test_no_permission(self, mock_remove, mock_walk): """Ignore if unable to delete files.""" mock_walk.return_value = self.files cl._delete_compiled_python_files() @mock.patch('os.walk') @mock.patch('os.remove', side_effect=OSError) def test_on_oserror(self, mock_remove, mock_walk): """Ignore if OSError.""" mock_walk.return_value = self.files cl._delete_compiled_python_files() @mock.patch('os.walk') @mock.patch('os.remove', side_effect=FileNotFoundError) def test_files_not_found(self, mock_remove, mock_walk): """Ignore if file not found.""" mock_walk.return_value = self.files cl._delete_compiled_python_files() @mock.patch('os.walk') @mock.patch('os.remove', side_effect=IsADirectoryError) def test__remove_dir(self, mock_remove, mock_walk): """Ignore if attempt to delete a directory.""" mock_walk.return_value = self.files cl._delete_compiled_python_files() class Test__delete_cache(unittest.TestCase): """proselint.command_line.__delete_cache().""" def setUp(self): """Init common data.""" self.cache_path = os.path.join("proselint", "cache") @mock.patch('shutil.rmtree') def test_rm_cache(self, mock_rmtree): """Correct directory is removed.""" cl._delete_cache() mock_rmtree.assert_called_with(self.cache_path) @mock.patch('shutil.rmtree', side_effect=PermissionError) def test_no_permission(self, mock_rmtree): """Ignore if unable to delete.""" cl._delete_cache() @mock.patch('shutil.rmtree', side_effect=OSError) def test_on_oserror(self, mock_rmtree): """Ignore if general OSError.""" cl._delete_cache() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/test_cliches.py0000644000000000000000000000333700000000000014674 0ustar00"""Test the Cliches.misc module.""" from proselint.checks.cliches import misc as chk from .check import Check class TestCheck(Check): """The test class for cliches.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def setUp(self): """Create test sentences.""" self.l_garner = """Worse than a fate worse than death.""" self.l_write_good = """He's a chip off the old block.""" self.l_gnu_diction = """It's a matter of concern.""" def test_cliches_garner_basic(self): """Basic checks on check_cliches_garner.""" assert chk.check_cliches_garner("""No cliches here.""") == [] # use one of the example cliches to verify basic functionality assert chk.check_cliches_garner(self.l_garner) != [] assert "cliches.garner" in chk.check_cliches_garner(self.l_garner)[0] def test_cliches_write_good_basic(self): """Basic checks on check_cliches_write_good.""" assert chk.check_cliches_write_good("""No cliches here.""") == [] # use one of the example cliches to verify basic functionality assert chk.check_cliches_write_good(self.l_write_good) != [] assert "cliches.write_good" in chk.check_cliches_write_good( self.l_write_good)[0] def test_cliches_gnu_diction_basic(self): """Basic check on check_cliches_gnu_diction.""" assert chk.check_cliches_gnu_diction("""No cliches here.""") == [] # use one of the example cliches to verify basic functionality assert chk.check_cliches_gnu_diction(self.l_gnu_diction) != [] assert "cliches.gnu_diction" in chk.check_cliches_gnu_diction( self.l_gnu_diction)[0] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/test_config_flag.py0000644000000000000000000000407100000000000015514 0ustar00"""Test user option overrides using --config and load_options""" import json import os from unittest import TestCase from unittest.mock import patch from click.testing import CliRunner from proselint.command_line import proselint from proselint.config import default from proselint.tools import deepmerge_dicts, load_options runner = CliRunner() def test_deepmerge_dicts(): """Test deepmerge_dicts""" d1 = {'a': 1, 'b': {'c': 2, 'd': 3}} d2 = {'a': 2, 'b': {'c': 3, 'e': 4}} assert deepmerge_dicts(d1, d2) == {'a': 2, 'b': {'c': 3, 'd': 3, 'e': 4}} @patch("os.path.isfile") def test_load_options_function(isfile): """Test load_options by specifying a user options path""" isfile.side_effect = "tests/test_config_flag_proselintrc".__eq__ 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").__eq__ TestCase().assertRaises(FileNotFoundError, load_options) def test_config_flag(): """Test the --config CLI argument""" output = runner.invoke(proselint, "--demo") assert "uncomparables.misc" in output.stdout output = runner.invoke( proselint, "--demo --config tests/test_config_flag_proselintrc") assert "uncomparables.misc" not in output.stdout output = runner.invoke(proselint, "--demo --config non_existent_file") assert output.exit_code == 1 assert "FileNotFoundError" == output.exc_info[0].__name__ output = runner.invoke(proselint, "non_existent_file") assert output.exit_code == 2 def test_dump_config(): """Test --dump-default-config and --dump-config""" output = runner.invoke(proselint, "--dump-default-config") assert json.loads(output.stdout) == default output = runner.invoke( proselint, "--dump-config --config tests/test_config_flag_proselintrc") assert json.loads(output.stdout) == json.load( open("tests/test_config_flag_proselintrc")) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/test_config_flag_proselintrc0000644000000000000000000000746500000000000017523 0ustar00{ "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 } } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/test_consistency_check.py0000644000000000000000000000156700000000000016763 0ustar00"""Test the consistency_check function from the tools.py module.""" from proselint.tools import consistency_check as chk from .check import Check class TestCheck(Check): """The test class for tools.consistency_check.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def setUp(self): """Create some test fixtures.""" self.L = [['colour', 'color']] self.err = 'error message' self.msg = 'inconsistent form of {} vs {}' def test_smoke(self): """Basic smoke test for consistency_check.""" assert chk( "Painting colour on color", self.L, self.err, self.msg) != [] assert chk( "Painting colour on colour", self.L, self.err, self.msg) == [] assert chk( "Painting color on color", self.L, self.err, self.msg) == [] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250509.186823 proselint-0.13.0/tests/test_consistency_spacing.py0000644000000000000000000000117500000000000017325 0ustar00"""Tests for consistency.spacing check.""" from proselint.checks.consistency import spacing as chk from .check import Check class TestCheck(Check): """The test class for consistency.spacing.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for consistency.spacing.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert self.passes( """This is good. Only one space each time. Every time.""") assert not self.passes("""This is bad. Not consistent. At all.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_consistency_spelling.py0000644000000000000000000000127400000000000017516 0ustar00"""Tests for consistency.spelling check.""" from proselint.checks.consistency import spelling as chk from .check import Check class TestCheck(Check): """The test class for consistency.spelling.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for consistency.spelling.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert self.passes("""The centre for the arts is the art centre.""") assert self.passes("""The center for the arts is the art center.""") assert not self.passes("""The centre of the arts is the art center.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_corporate_speak.py0000644000000000000000000000112600000000000016435 0ustar00"""Tests for corporate_speak.misc check.""" from proselint.checks.corporate_speak import misc as chk from .check import Check class TestCheck(Check): """The test class for corporate_speak.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for corporate_speak.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert self.passes("""We will discuss it later.""") assert not self.passes("""We will circle back around to it.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_cursing_filth.py0000644000000000000000000000076500000000000016124 0ustar00"""Tests for cursing.filth check.""" from proselint.checks.cursing import filth as chk from .check import Check class TestCheck(Check): """The test class for cursing.filth.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for cursing.filth.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""Bad shit in this phrase.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_cursing_nfl.py0000644000000000000000000000076100000000000015571 0ustar00"""Tests for cursing.nfl check.""" from proselint.checks.cursing import nfl as chk from .check import Check class TestCheck(Check): """The test class for cursing.nfl.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for cursing.nfl.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""The QB is named ball licker.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_cursing_nword.py0000644000000000000000000000075000000000000016141 0ustar00"""Tests for cursing.nword check.""" from proselint.checks.cursing import nword as chk from .check import Check class TestCheck(Check): """The test class for cursing.nword.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for cursing.nword.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""The n-word.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_dates_times_am_pm.py0000644000000000000000000000412600000000000016731 0ustar00"""Tests for dates_times.am_pm check.""" from proselint.checks.dates_times import am_pm as chk from .check import Check class TestCheck(Check): """The test class for dates_times.am_pm.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke_check_lowercase_periods(self): """Basic smoke test. This is for the function dates_times.am_pm.check_lowercase_periods. """ assert chk.check_lowercase_periods( "Basic smoke phrase without issues.") == [] assert chk.check_lowercase_periods( "It happened at 7 a.m.") == [] assert chk.check_lowercase_periods( "It happened at 7 am.") != [] assert chk.check_lowercase_periods( "On Wed, Sep 21, 2016 at 11:42 AM -0400, X wrote:") == [] def test_smoke_check_spacing(self): """Basic smoke test. This is for the function dates_times.am_pm.check_spacing. """ assert chk.check_spacing( "Basic smoke phrase without issues.") == [] assert chk.check_spacing( "It happened at 7 a.m.") == [] assert chk.check_spacing( "It happened at 7a.m.") != [] def test_smoke_check_midnight_noon(self): """Basic smoke test. This for the function dates_times.am_pm.midnight_noon. """ assert chk.check_midnight_noon( "Basic smoke phrase without issues.") == [] assert chk.check_midnight_noon( "It happened at noon.") == [] assert chk.check_midnight_noon( "It happened at 12 a.m.") != [] def test_smoke_check_redundancy(self): """Basic smoke test. This for the function dates_times.am_pm.check_redundancy. """ assert len(chk.check_redundancy( "Basic smoke phrase without issues.")) == 0 assert len(chk.check_redundancy( "It happened at 7 a.m.")) == 0 assert len(chk.check_redundancy( "It happened at 7a.m. in the morning.")) == 1 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_dates_times_dates.py0000644000000000000000000000507700000000000016746 0ustar00"""Tests for dates_times.dates check.""" from proselint.checks.dates_times import dates as chk from .check import Check class TestCheck(Check): """The test class for dates_times.dates.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke_check_decade_apostrophes_short(self): """Basic smoke test. This for the function dates_times.dates.check_decade_apostrophes_short. """ assert chk.check_decade_apostrophes_short( "Basic smoke phrase without issues.") == [] assert chk.check_decade_apostrophes_short( "It happened in the 90s.") == [] assert chk.check_decade_apostrophes_short( "It happened in the 90's.") != [] def test_smoke_check_decade_apostrophes_long(self): """Basic smoke test. This is for the function dates_times.dates.decade_apostrophes_long. """ assert chk.check_decade_apostrophes_long( "Basic smoke phrase without issues.") == [] assert chk.check_decade_apostrophes_long( "It happened in the 1980s.") == [] assert chk.check_decade_apostrophes_long( "It happened in the 1980's.") != [] def test_smoke_check_dash_and_from(self): """Basic smoke test. This for the function dates_times.dates.dash_and_from. """ assert chk.check_dash_and_from( "Basic smoke phrase without issues.") == [] assert chk.check_dash_and_from( "It happened from 2000 to 2005.") == [] assert chk.check_dash_and_from( "It happened from 2000-2005.") != [] def test_smoke_check_month_year_comma(self): """Basic smoke test. This is for the function dates_times.dates.check_month_year_comma. """ assert chk.check_month_year_comma( "Basic smoke phrase without issues.") == [] assert chk.check_month_year_comma( "It happened in August 2008.") == [] assert chk.check_month_year_comma( "It happened in August, 2008.") != [] def test_smoke_check_month_of_year(self): """Basic smoke test. This is for the function dates_times.dates.check_month_of_year. """ assert chk.check_month_of_year( "Basic smoke phrase without issues.") == [] assert chk.check_month_of_year( "It happened in August 2008.") == [] assert chk.check_month_of_year( "It happened in August of 2008.") != [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_existence_check.py0000644000000000000000000000470100000000000016402 0ustar00"""Test the existence_check function from the tools.py module.""" from __future__ import absolute_import from proselint.tools import existence_check as chk from .check import Check class TestCheck(Check): """The test class for tools.existence_check.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def setUp(self): """setUp method creating some test fixtures.""" self.L = ['abc'] self.err = 'error message' self.msg = 'it exists' def test_smoke(self): """Basic smoke test to determine that existence_check is working.""" assert chk( """abc is as easy as 123""", self.L, self.err, self.msg) != [] assert chk( """easy breezy""", self.L, self.err, self.msg) == [] assert self.err in chk( """abc is as easy as 123""", self.L, self.err, self.msg)[0] assert self.msg in chk( """abc is as easy as 123""", self.L, self.err, self.msg)[0] def test_multiple_matches(self): """Test that multiple matches are found correctly.""" assert len( chk("""abc and abc are as easy as 123""", self.L, self.err, self.msg)) == 2 assert len( chk("""ABC and abc are as easy as 123""", self.L, self.err, self.msg, ignore_case=True)) == 2 assert len( chk("""ABC and abc are as easy as 123""", self.L, self.err, self.msg, ignore_case=False)) == 1 assert chk( """abcabc are easy as 123""", self.L, self.err, self.msg) == [] def test_string_types(self): """Test that the function works with different string types.""" assert chk('abc is easy as 123', self.L, self.err, self.msg) != [] assert chk("abc is easy as 123", self.L, self.err, self.msg) != [] assert chk(u'abc is easy as 123', self.L, self.err, self.msg) != [] assert chk(u"abc is easy as 123", self.L, self.err, self.msg) != [] def test_exceptions(self): """Test that existence_check does not report excluded phrases""" regex = [r"\b(\w+)\b\s\1"] no = ["should should"] errs = chk("should should flag flag.", regex, "", "", require_padding=False) assert len(errs) == 2 errs = chk("should should flag flag.", regex, "", "", exceptions=no, require_padding=False) assert len(errs) == 1 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_exit_codes.py0000644000000000000000000000132500000000000015403 0ustar00"""Check that the CLI returns the appropriate exit code.""" from click.testing import CliRunner from proselint.command_line import proselint from .check import Check class TestExitCodes(Check): """Test class for CLI exit codes""" __test__ = True def setUp(self): self.runner = CliRunner() def test_exit_code_demo(self): """Ensure that linting the demo returns an exit code of 1.""" output = self.runner.invoke(proselint, "--demo") assert output.exit_code == 1 def test_exit_code_version(self): """Ensure that getting the version returns an exit code of 0.""" output = self.runner.invoke(proselint, "--version") assert output.exit_code == 0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_garner_dates.py0000644000000000000000000000176400000000000015722 0ustar00"""Test garner.dates.""" from proselint.checks.dates_times import dates from .check import Check class TestCheck(Check): """Test class for garner.dates.""" __test__ = True def test_50s_hyphenation(self): """Find unneeded hyphen in 50's.""" text = """The 50's were swell.""" errors = dates.check_decade_apostrophes_short(text) assert len(errors) == 1 def test_50_Cent_hyphenation(self): """Don't flag 50's when it refers to 50 Cent's manager.""" text = """ Dr. Dre suggested to 50's manager that he look into signing Eminem to the G-Unit record label. """ errors = dates.check_decade_apostrophes_short(text) assert len(errors) == 0 def test_dash_and_from(self): """Test garner.check_dash_and_from.""" text = """From 1999-2002, Sally served as chair of the committee.""" errors = dates.check_dash_and_from(text) print(errors) assert len(errors) == 1 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_gmeu/test_a_A.py0000644000000000000000000000436700000000000015742 0ustar00#!/usr/bin/env python """Test GMEU entry 'a', part A.""" from proselint.tools import assert_error from tests.check import Check class chk: def check(self, text): return assert_error, text, "misc.a_vs_an" class TestCheck(Check): """The test class for GMEU entry A - using the correct determiner.""" @property def this_check(self): return chk sentences_with_errors = [ "Are you an Yankee fan?", "Coffee tastes less sweet in an white mug.", "One of them wore a opalescent vest.", "You're a intelligent guy, often misguided.", "Ezra gave an eulogy.", "What capital of an European country is the farthest north?", "His sole reward was an one-year term as Ambassador to Thailand.", "I will be relying on a roulette wheel and an Ouija board.", "Anyone in an uniform is fair game.", "Grimm started working as a F.B.I. agent in 1995.", "Out of 186 managers participating, 57 had a MBA degree.", "Smith announced that a SEC filing is pending.", "This argument is an historical desecration.", "The treatment of crime in Britain shows an historic shift away…", "It is, in some ways, an humble form.", "The thief turned out to be an habitual offender from Darlington.", "This stage displays an hallucinatory image that signifies itself.", "He saw an hallucinatory image before passing out.", "A triumphant Adolf Hitler addressed an hysterical crowd.", "Kun pieced together an history of gender-segregated dining in L.A.", "An historian who fled the Nazis and still wants us to read Hitler.", "It is nominally an historical novel.", "It feels good to validate an hypothesis.", "An hereditary title can be passed to a member of the family.", "The Clinton Presidency was an historic era of prosperity.", "There comes to be an habitual pattern between neurons in the brain.", "They had the authority to start an humanitarian intervention.", "She laughed aloud, an hysterical sort of giggled, quickly stifled", ] def test_smoke(self): for sentence in self.sentences_with_errors: assert not self.passes(sentence) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_gmeu/test_a_B.py0000644000000000000000000000155700000000000015741 0ustar00#!/usr/bin/env python """Test GMEU entry 'a', part B.""" from proselint.tools import assert_error from tests.check import Check class chk: def check(self, text): return assert_error, text, "misc.a_distributive" class TestCheck(Check): """The test class for GMEU entry A - using a over per in the distributive sense. """ @property def this_check(self): return chk def test_smoke(self): sentences = [ "An apple per day keeps the doctor away.", "I sleep eight hours per night.", "Their policy allows one golf cart a couple.", "The company donated five books a student.", "Our a-unit cost is less than $1000.", "The $50-a-parent fee seems unreasonably high." ] for sentence in sentences: assert not self.passes(sentence) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_gmeu/test_a_C.py0000644000000000000000000000025300000000000015732 0ustar00#!/usr/bin/env python """Test GMEU entry 'a', part C. This entry is about pronunciation of the word 'a' and is unlikely to be relevant to usage in written language. """ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_hedging.py0000644000000000000000000000100000000000000014650 0ustar00"""Tests for hedging.misc check.""" from proselint.checks.hedging import misc as chk from .check import Check class TestCheck(Check): """The test class for hedging.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for hedging.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""I would argue that this is a good test.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_hyperbole.py0000644000000000000000000000076200000000000015252 0ustar00"""Tests for hyperbole.misc check.""" from proselint.checks.hyperbole import misc as chk from .check import Check class TestCheck(Check): """The test class for hyperbole.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for hyperbole.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""So exaggerated!!!""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_illegal_chars.py0000644000000000000000000000135200000000000016046 0ustar00"""Check that the CLI can handle invalid characters.""" from os.path import abspath, dirname, join from click.testing import CliRunner from proselint.command_line import proselint from .check import Check class TestInvalidCharacters(Check): """Test class for testing invalid characters on the CLI""" __test__ = True def test_invalid_characters(self): """Ensure that a file with illegal characters does not break us.""" curr_dir = dirname(abspath(__file__)) test_file = join(curr_dir, "illegal-chars.txt") runner = CliRunner() output = runner.invoke(proselint, test_file) assert "UnicodeDecodeError" not in output.stdout assert "FileNotFoundError" not in output.stdout ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_jargon.py0000644000000000000000000000076500000000000014544 0ustar00"""Tests for jargon.misc check.""" from proselint.checks.jargon import misc as chk from .check import Check class TestCheck(Check): """The test class for jargon.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for jargon.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""I agree it's in the affirmative.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_leonard.py0000644000000000000000000000142600000000000014703 0ustar00"""Test garner.dates.""" from proselint.checks.typography import exclamation from .check import Check class TestCheck(Check): """Test class for leonard.exclamation.""" __test__ = True def test_capitalization_and_no_exclamation(self): """Don't throw error when phrase has capitalization.""" text = """ The QUICK BROWN fox juMPED over the lazy cat. """ errors = exclamation.check_repeated_exclamations(text) assert len(errors) == 0 def test_exclamation(self): """Test leonard.exclamation. with exclamation marks.""" text = """Sally sells seashells and they were too expensive!!!!""" errors = exclamation.check_repeated_exclamations(text) print(errors) assert len(errors) == 1 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_lexical_illusions.py0000644000000000000000000000171000000000000016775 0ustar00"""Tests for lexical_illusions.misc check.""" from proselint.checks.lexical_illusions import misc as chk from .check import Check class TestCheck(Check): """The test class for lexical_illusions.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for lexical_illusions.misc.""" assert self.passes("Smoke phrase with nothing flagged.") assert not self.passes("Paris in the the springtime.") assert self.passes("And he's gone, gone with the breeze") assert self.passes("You should know that that sentence wasn't wrong.") assert self.passes("She had had dessert on the balcony.") assert not self.passes("You should know that that that was wrong.") assert self.passes("The practitioner's side") assert self.passes("An antimatter particle") assert self.passes("The theory") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_lgbtq_offensive_terms.py0000644000000000000000000000107400000000000017645 0ustar00"""Tests for lgbtq.terms check.""" from proselint.checks.lgbtq import offensive_terms as chk from .check import Check class TestCheck(Check): """The test class for lgbtq.offensive_terms.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for lgbtq.offensive_terms.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert self.passes("""I once met a gay man.""") assert not self.passes("""I once met a fag.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_lgbtq_terms.py0000644000000000000000000000155300000000000015603 0ustar00"""Tests for lgbtq.offensive_terms check.""" from proselint.checks.lgbtq import terms as chk from .check import Check class TestCheck(Check): """The test class for lgbtq.terms.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for lgbtq.terms.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert self.passes("""They were a gay couple.""") assert not self.passes("""He was a homosexual man.""") def test_homosexual_term(self): """Check that the term homosexual does not get caught.""" assert self.passes("""Homosexual.""") def test_sexual_prefence(self): """Check that sexual preference is flagged.""" assert not self.passes("""My sexual preference is for women.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_malaproprisms.py0000644000000000000000000000102400000000000016142 0ustar00"""Tests for malaproprisms.misc check.""" from proselint.checks.malapropisms import misc as chk from .check import Check class TestCheck(Check): """The test class for malaproprisms.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for malaproprisms.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""Found in the Infinitesimal Universe.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_apologizing.py0000644000000000000000000000100100000000000016601 0ustar00"""Tests for misc.apologizing check.""" from proselint.checks.misc import apologizing as chk from .check import Check class TestCheck(Check): """The test class for misc.apologizing.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.apologizing.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""More research is needed.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_back_formations.py0000644000000000000000000000102400000000000017425 0ustar00"""Tests for misc.back_formations check.""" from proselint.checks.misc import back_formations as chk from .check import Check class TestCheck(Check): """The test class for misc.back_formations.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.back_formations.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""It is an improprietous use.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_bureaucratese.py0000644000000000000000000000105100000000000017116 0ustar00"""Tests for misc.bureaucratese check.""" from proselint.checks.misc import bureaucratese as chk from .check import Check class TestCheck(Check): """The test class for misc.bureaucratese.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.bureaucratese.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes( """I hope the report meets with your approval.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_but.py0000644000000000000000000000113000000000000015054 0ustar00"""Tests for misc.but check.""" from proselint.checks.misc import but as chk from .check import Check class TestCheck(Check): """The test class for misc.but.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.but.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""But I never start with the word "but".""") assert self.passes("""I never start with the word "but", but might use it after a linebreak.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_capitalization.py0000644000000000000000000000201600000000000017301 0ustar00"""Tests for misc.capitalization check.""" from proselint.checks.misc import capitalization as chk from .check import Check class TestCheck(Check): """The test class for misc.capitalization.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.capitalization.check.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""It goes back to the stone age.""") def test_smoke_check_months(self): """Basic smoke test for misc.capitalization.check_months.""" assert chk.check_months("""Smoke phrase with nothing flagged""") == [] assert chk.check_months("""A nice day in june.""") != [] def test_smoke_check_days(self): """Basic smoke test for misc.capitalization.check_days.""" assert chk.check_days("""Smoke phrase with nothing flagged""") == [] assert chk.check_days("""It happened on friday.""") != [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_chatspeak.py0000644000000000000000000000076400000000000016241 0ustar00"""Tests for misc.chatspeak check.""" from proselint.checks.misc import chatspeak as chk from .check import Check class TestCheck(Check): """The test class for misc.chatspeak.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.chatspeak.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""BRB getting coffee.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_commercialese.py0000644000000000000000000000102100000000000017071 0ustar00"""Tests for misc.commercialese check.""" from proselint.checks.misc import commercialese as chk from .check import Check class TestCheck(Check): """The test class for misc.commercialese.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.commercialese.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""We regret to inform you of this.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_composition.py0000644000000000000000000000100100000000000016622 0ustar00"""Tests for misc.composition check.""" from proselint.checks.misc import composition as chk from .check import Check class TestCheck(Check): """The test class for misc.composition.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.composition.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""His story is not honest.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_currency.py0000644000000000000000000000076100000000000016125 0ustar00"""Tests for misc.currency check.""" from proselint.checks.misc import currency as chk from .check import Check class TestCheck(Check): """The test class for misc.currency.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.currency.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""It cost $10 dollars.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_debased.py0000644000000000000000000000077000000000000015662 0ustar00"""Tests for misc.debased check.""" from proselint.checks.misc import debased as chk from .check import Check class TestCheck(Check): """The test class for misc.debased.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.debased.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""This leaves much to be desired.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_false_plurals.py0000644000000000000000000000140000000000000017116 0ustar00"""Tests for misc.false_plurals check.""" from proselint.checks.misc import false_plurals as chk from .check import Check class TestCheck(Check): """The test class for misc.false_plurals.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.false_plurals.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""There were several phenomenons.""") def test_smoke_kudos(self): """Basic smoke test for misc.false_plurals.kudos.""" assert chk.check_kudos("""Smoke phrase with nothing flagged.""") == [] assert chk.check_kudos("""I give you many kudos.""") != [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_greylist.py0000644000000000000000000000077600000000000016143 0ustar00"""Tests for misc.greylist check.""" from proselint.checks.misc import greylist as chk from .check import Check class TestCheck(Check): """The test class for misc.greylist.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.greylist.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""She should utilize her knowledge.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_illogic.py0000644000000000000000000000221000000000000015704 0ustar00"""Tests for misc.illogic check.""" from proselint.checks.misc import illogic as chk from .check import Check class TestCheck(Check): """The test class for misc.illogic.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.illogic.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""We should preplan the trip.""") def test_smoke_coin_a_phrase_from(self): """Basic smoke test for misc.illogic.check_coin_a_phrase_from.""" assert chk.check_coin_a_phrase_from( """Smoke phrase with nothing flagged.""") == [] assert chk.check_coin_a_phrase_from( """To coin a phrase from him, No diggity""") != [] def test_smoke_check_without_your_collusion(self): """Basic smoke test for misc.illogic.""" assert chk.check_without_your_collusion( """Smoke phrase with nothing flagged.""") == [] assert chk.check_without_your_collusion( """Not Without your collusion you won't'.""") != [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_inferior_superior.py0000644000000000000000000000107000000000000020032 0ustar00"""Tests for misc.inferior_superior check.""" from proselint.checks.misc import inferior_superior as chk from .check import Check class TestCheck(Check): """The test class for misc.inferior_superior.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.inferior_superior.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes( """It was more inferior than the alternative.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_latin.py0000644000000000000000000000076200000000000015403 0ustar00"""Tests for misc.latin check.""" from proselint.checks.misc import latin as chk from .check import Check class TestCheck(Check): """The test class for misc.latin.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.latin.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""And ceteris paribus, it was good.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_many_a.py0000644000000000000000000000101300000000000015526 0ustar00"""Tests for misc.many_a check.""" from proselint.checks.misc import many_a as chk from .check import Check class TestCheck(Check): """The test class for misc.many_a.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.many_a.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes( """There were many a day I thought about it.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_metaconcepts.py0000644000000000000000000000061600000000000016757 0ustar00"""Tests for misc.metaconcepts check.""" from proselint.checks.misc import metaconcepts as chk from .check import Check class TestCheck(Check): """The test class for misc.metaconcepts.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.metaconcepts.""" pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_metadiscourse.py0000644000000000000000000000103000000000000017130 0ustar00"""Tests for misc.metadiscourse check.""" from proselint.checks.misc import metadiscourse as chk from .check import Check class TestCheck(Check): """The test class for misc.metadiscourse.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.metadiscourse.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""It's based on the rest of this article.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_narcissism.py0000644000000000000000000000112600000000000016442 0ustar00"""Tests for misc.narcissism check.""" from proselint.checks.misc import narcissism as chk from .check import Check class TestCheck(Check): """The test class for misc.narcissism.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.narcissism.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes( """In recent years, an increasing number of scientists have studied the problem in detail.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_not_guilty.py0000644000000000000000000000103600000000000016464 0ustar00"""Tests for misc.not_guilty check.""" from proselint.checks.misc import not_guilty as chk from .check import Check class TestCheck(Check): """The test class for misc.not_guilty.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.not_guilty.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes( """She is not guilty beyond a reasonable doubt.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_phrasal_adjectives.py0000644000000000000000000000160400000000000020123 0ustar00"""Tests for misc.phrasal_adjectives check.""" from proselint.checks.misc import phrasal_adjectives as chk from .check import Check class TestCheck(Check): """The test class for misc.phrasal_adjectives.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.phrasal_adjectives.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""There were across the board discounts.""") def test_smoke_ly(self): """Basic smoke test for misc.phrasal_adjectives.check_ly.""" assert chk.check_ly("""Smoke phrase with nothing flagged.""") == [] assert chk.check_ly("""He ran swiftly-fast.""") assert chk.check_ly("""The not-so-hotly-contested result was fine.""") == [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_preferred_forms.py0000644000000000000000000000102000000000000017444 0ustar00"""Tests for misc.preferred_forms check.""" from proselint.checks.misc import preferred_forms as chk from .check import Check class TestCheck(Check): """The test class for misc.preferred_forms.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.preferred_forms.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""It was almost haloween.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_pretension.py0000644000000000000000000000101400000000000016451 0ustar00"""Tests for misc.pretension check.""" from proselint.checks.misc import pretension as chk from .check import Check class TestCheck(Check): """The test class for misc.pretension.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.pretension.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""We need to reconceptualize the project.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_professions.py0000644000000000000000000000101100000000000016632 0ustar00"""Tests for misc.professions check.""" from proselint.checks.misc import professions as chk from .check import Check class TestCheck(Check): """The test class for misc.professions.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.professions.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""I really need a shoe repair guy.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_punctuation.py0000644000000000000000000000077200000000000016646 0ustar00"""Tests for misc.punctuation check.""" from proselint.checks.misc import punctuation as chk from .check import Check class TestCheck(Check): """The test class for misc.punctuation.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.punctuation.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""See Smith et. al.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_scare_quotes.py0000644000000000000000000000101600000000000016762 0ustar00"""Tests for misc.scare_quotes check.""" from proselint.checks.misc import scare_quotes as chk from .check import Check class TestCheck(Check): """The test class for misc.scare_quotes.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.scare_quotes.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""What was the 'take-home message'?""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_suddenly.py0000644000000000000000000000077100000000000016123 0ustar00"""Tests for misc.suddenly check.""" from proselint.checks.misc import suddenly as chk from .check import Check class TestCheck(Check): """The test class for misc.suddenly.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.suddenly.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""Suddenly, it all made sense.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_tense_present.py0000644000000000000000000000101700000000000017144 0ustar00"""Tests for misc.tense_present check.""" from proselint.checks.misc import tense_present as chk from .check import Check class TestCheck(Check): """The test class for misc.tense_present.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.tense_present.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""I did it on accident honestly.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_usage.py0000644000000000000000000000056200000000000015376 0ustar00"""Tests for misc.usage check.""" from proselint.checks.misc import usage as chk from .check import Check class TestCheck(Check): """The test class for misc.usage.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.usage.""" pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_waxed.py0000644000000000000000000000076200000000000015404 0ustar00"""Tests for misc.waxed check.""" from proselint.checks.misc import waxed as chk from .check import Check class TestCheck(Check): """The test class for misc.waxed.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.waxed.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""They really could wax poetically.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_misc_whence.py0000644000000000000000000000076200000000000015545 0ustar00"""Tests for misc.whence check.""" from proselint.checks.misc import whence as chk from .check import Check class TestCheck(Check): """The test class for misc.whence.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for misc.whence.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""Go back from whence you came!""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_mixed_metaphors.py0000644000000000000000000000154200000000000016446 0ustar00"""Tests for mixed_metaphors.misc check.""" from proselint.checks.mixed_metaphors import misc as chk from .check import Check class TestCheck(Check): """The test class for mixed_metaphors.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke_bottleneck(self): """Basic smoke test for check_bottleneck.""" assert chk.check_bottleneck( """Smoke phrase with nothing flagged.""") == [] assert chk.check_bottleneck( """The project produced a huge bottleneck.""") != [] def test_smoke_misc(self): """Basic smoke test for check_misc.""" assert chk.check_misc( """Smoke phrase with nothing flagged.""") == [] assert chk.check_misc( """Writing tests is not rocket surgery.""") != [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_mondegreens.py0000644000000000000000000000110000000000000015552 0ustar00"""Tests for mondegreens.misc check.""" from proselint.checks.mondegreens import misc as chk from .check import Check class TestCheck(Check): """The test class for mondegreens.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for mondegreens.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert self.passes("""... and laid him on the green.""") assert not self.passes("""..and Lady Mondegreen.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_needless_variants.py0000644000000000000000000000104000000000000016760 0ustar00"""Tests for needless_variants.misc check.""" from proselint.checks.needless_variants import misc as chk from .check import Check class TestCheck(Check): """The test class for needless_variants.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for needless_variants.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""It was an extensible telescope.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_nonwords.py0000644000000000000000000000077400000000000015135 0ustar00"""Tests for nonwords.misc check.""" from proselint.checks.nonwords import misc 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_smoke(self): """Basic smoke test for nonwords.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""The test was good irregardless.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_oxymorons.py0000644000000000000000000000077500000000000015342 0ustar00"""Tests for oxymorons.misc check.""" from proselint.checks.oxymorons import misc as chk from .check import Check class TestCheck(Check): """The test class for oxymorons.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for oxymorons.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""He needed an exact estimate.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_preferred_forms_check.py0000644000000000000000000000224400000000000017577 0ustar00"""Test the preferred_forms_check function from the tools.py module.""" from proselint.tools import preferred_forms_check as chk from .check import Check class TestCheck(Check): """The test class for tools.preferred_forms_check.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def setUp(self): """Create some test fixtures.""" self.L = [['use', ['utilize']]] self.l_caps = [["Stone Age", ["stone age"]]] self.err = 'error message' self.msg = 'use the preferred form' def test_smoke(self): """Basic smoke test for preferred_forms_check.""" assert chk( "We utilize this tech", self.L, self.err, self.msg) != [] assert chk( "We use this tech", self.L, self.err, self.msg) == [] def test_capitalization(self): """Test for preferred forms involving capitalization.""" assert not chk( "In the stone age", self.l_caps, self.err, self.msg, ignore_case=False) assert chk( "In the Stone Age", self.l_caps, self.err, self.msg, ignore_case=False) == [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_psychology.py0000644000000000000000000000240500000000000015455 0ustar00"""Tests for psychology.misc check.""" from proselint.checks.psychology import misc as chk from .check import Check class TestCheck(Check): """The test class for psychology.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke_lie_detector_test(self): """Basic smoke test for psychology.misc.check_lie_detector_test.""" assert chk.check_lie_detector_test( """Smoke phrase with nothing flagged.""") == [] assert chk.check_lie_detector_test( """The defendant took a lie detector test.""") != [] def test_smoke_p_equals_zero(self): """Basic smoke test for psychology.misc.check_p_equals_zero.""" assert chk.check_p_equals_zero( """Smoke phrase with nothing flagged.""") == [] assert chk.check_p_equals_zero( """The effect was highly signficant at p = 0.00.""") != [] def test_smoke_mental_telepathy(self): """Basic smoke test for psychology.misc.check_mental_telepathy.""" assert chk.check_mental_telepathy( """Smoke phrase with nothing flagged.""") == [] assert chk.check_mental_telepathy( """I've been practicing mental telepathy.""") != [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_redundancy_misc.py0000644000000000000000000000257600000000000016435 0ustar00"""Tests for redundancy.misc check.""" from proselint.checks.redundancy import misc as chk from .check import Check class TestCheck(Check): """The test class for redundancy.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke_check(self): """Basic smoke test for redundancy.misc.check.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""The table was rectangular in shape.""") def test_smoke_garner(self): """Basic smoke test for redundancy.misc.check_garner.""" assert chk.check_garner( """Smoke phrase with nothing flagged.""") == [] assert chk.check_garner( """It was blatantly obvious what to do next.""") != [] def test_smoke_nordquist(self): """Basic smoke test for redundancy.misc.check_norquist.""" assert chk.check_nordquist( """Smoke phrase with nothing flagged.""") == [] assert chk.check_nordquist( """Taking the package was absolutely essential.""") != [] def test_smoke_atd(self): """Basic smoke test for redundancy.misc.check_norquist.""" assert chk.check_atd( """Smoke phrase with nothing flagged.""") == [] assert chk.check_atd( """He often repeated the old adage.""") != [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_redundancy_ras_syndrome.py0000644000000000000000000000104200000000000020172 0ustar00"""Tests for redundancy.ras_syndrome check.""" from proselint.checks.redundancy import ras_syndrome as chk from .check import Check class TestCheck(Check): """The test class for redundancy.ras_syndrome.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for redundancy.ras_syndrome.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""Please enter your PIN number.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_security_credit_card.py0000644000000000000000000000115600000000000017451 0ustar00"""Tests for security.credit_card check.""" from proselint.checks.security import credit_card as chk from .check import Check class TestCheck(Check): """The test class for security.credit_card.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for security.credit_card. This makes use of a test MasterCard number. """ assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes( """My credit card number is 5555555555554444.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_security_password.py0000644000000000000000000000110300000000000017040 0ustar00"""Tests for security.password check.""" from proselint.checks.security import password as chk from .check import Check class TestCheck(Check): """The test class for security.password.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for security.password.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""The password is 123456.""") assert not self.passes("""My password is PASSWORD.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_sexism.py0000644000000000000000000000102300000000000014560 0ustar00"""Tests for sexism.misc check.""" from proselint.checks.sexism import misc as chk from .check import Check class TestCheck(Check): """The test class for sexism.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for sexism.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes( """The legal team had two lawyers and a lady lawyer.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_skunked_terms.py0000644000000000000000000000105600000000000016134 0ustar00"""Tests for skunked_terms.misc check.""" from proselint.checks.skunked_terms import misc as chk from .check import Check class TestCheck(Check): """The test class for skunked_terms.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for skunked_terms.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes( """I gave an impassionate defence of the situation.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_spelling_able_atable.py0000644000000000000000000000103700000000000017365 0ustar00"""Tests for spelling.able_atable check.""" from proselint.checks.spelling import able_atable as chk from .check import Check class TestCheck(Check): """The test class for spelling.able_atable.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for spelling.able_atable.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""There was a demonstratable difference.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_spelling_able_ible.py0000644000000000000000000000101400000000000017043 0ustar00"""Tests for spelling.able_ible check.""" from proselint.checks.spelling import able_ible as chk from .check import Check class TestCheck(Check): """The test class for spelling.able_ible.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for spelling.able_ible.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""It was a sensable decision.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_spelling_athletes.py0000644000000000000000000000101300000000000016755 0ustar00"""Tests for spelling.athletes check.""" from proselint.checks.spelling import athletes as chk from .check import Check class TestCheck(Check): """The test class for spelling.athletes.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for spelling.athletes.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""One of the greats: Cal Ripkin.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_spelling_em_im_en_in.py0000644000000000000000000000102500000000000017405 0ustar00"""Tests for spelling.em_im_en_in check.""" from proselint.checks.spelling import em_im_en_in as chk from .check import Check class TestCheck(Check): """The test class for spelling.em_im_en_in.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for spelling.em_im_en_in.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""We shall imbark on a voyage.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_spelling_er_or.py0000644000000000000000000000077300000000000016266 0ustar00"""Tests for spelling.er_or check.""" from proselint.checks.spelling import er_or as chk from .check import Check class TestCheck(Check): """The test class for spelling.er_or.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for spelling.er_or.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""She met with the invester.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_spelling_in_un.py0000644000000000000000000000077100000000000016266 0ustar00"""Tests for spelling.in_un check.""" from proselint.checks.spelling import in_un as chk from .check import Check class TestCheck(Check): """The test class for spelling.in_un.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for spelling.in_un.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""The plan was unfeasible.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_spelling_misc.py0000644000000000000000000000075600000000000016114 0ustar00"""Tests for spelling.misc check.""" from proselint.checks.spelling import misc as chk from .check import Check class TestCheck(Check): """The test class for spelling.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for spelling.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""I like this alot.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_strunk_white_eos.py0000644000000000000000000000130300000000000016645 0ustar00"""Unit tests for strunk_white_eos.""" # from __future__ import absolute_import # from .check import Check # from proselint.checks.strunkwhite import elementary_composition as chk # class TestCheck(Check): # """Define the suite of checks.""" # __test__ = True # @property # def this_check(self): # return chk # def test_with_utilized(self): # """Don't produce an error when 'use' is used correctly.""" # assert self.check("I use a hammer to drive nails into wood.") # def test_no_utilized(self): # """Produce an error when 'utilize' is used in place of 'use'.""" # assert not self.check("I utilize a hammer to drive nails into wood.") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_terms_animal_adjectives.py0000644000000000000000000000104400000000000020127 0ustar00"""Tests for terms.animal_adjectives check.""" from proselint.checks.terms import animal_adjectives as chk from .check import Check class TestCheck(Check): """The test class for terms.animal_adjectives.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for terms.animal_adjectives.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""It was some bird-like creature.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_terms_denizen_labels.py0000644000000000000000000000103200000000000017440 0ustar00"""Tests for terms.denizen_labels check.""" from proselint.checks.terms import denizen_labels as chk from .check import Check class TestCheck(Check): """The test class for terms.denizen_labels.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for terms.denizen_labels.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""He was definitely a Hong Kongite.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_terms_eponymous_adjectives.py0000644000000000000000000000106200000000000020724 0ustar00"""Tests for terms.eponymous_adjectives check.""" from proselint.checks.terms import eponymous_adjectives as chk from .check import Check class TestCheck(Check): """The test class for terms.eponymous_adjectives.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for terms.eponymous_adjectives.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""The writing wasn't Shakespearian.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_terms_venery.py0000644000000000000000000000077100000000000016003 0ustar00"""Tests for terms.venery check.""" from proselint.checks.terms import venery as chk from .check import Check class TestCheck(Check): """The test class for terms.venery.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for terms.venery.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""There was a group of alligators.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_tools.py0000644000000000000000000000217400000000000014420 0ustar00"""Test the tools module.""" from proselint.config import default from proselint.tools import lint as proselint, load_options from .check import Check def lint(text): return proselint(text, config=load_options(conf_default=default)) class TestLint(Check): """The test class for tools.lint.""" __test__ = True def setUp(self): """setUp method creating text fixtures.""" self.text = """But this is a very bad sentence. This is also a no-good sentence. """ self.text_with_no_newline = """A very bad sentence.""" def extract_line_col(self, error): """Extract the line and column number from an error tuple.""" _, _, line, column, _, _, _, _, _ = error return line, column def test_errors_sorted(self): """Test that errors are sorted by line and column number.""" lines_and_cols = [self.extract_line_col(e) for e in lint(self.text)] assert sorted(lines_and_cols) == lines_and_cols def test_on_no_newlines(self): """Test that lint works on text without a terminal newline.""" assert len(lint(self.text_with_no_newline)) == 1 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_topic_detector.py0000644000000000000000000000130500000000000016262 0ustar00"""Test the topic detector tool.""" from proselint.tools import topics def test_50_Cent_detector_on_topic(): """Check precision of the 50 Cent topic-detector.""" text = """With the aid of Eminem and Dr. Dre (who produced his first major-label album, Get Rich or Die Tryin'), Jackson became one of the world's best selling rappers and rose to prominence with East Coast hip hop group G-Unit (which he leads de facto). """ assert("50 Cent" in topics(text)) text = """Hip hop was started in the early 50's.""" assert("50 Cent" not in topics(text)) text = """Nowadays it costs 50 cents to buy a lollipop.""" assert("50 Cent" not in topics(text)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_typography_diacritical_marks.py0000644000000000000000000000107300000000000021210 0ustar00"""Tests for typography.diacritical_marks check.""" from proselint.checks.typography import diacritical_marks as chk from .check import Check class TestCheck(Check): """The test class for typography.diacritical_marks.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for typography.diacritical_marks.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""He saw the performance by Beyonce.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_typography_exclamation.py0000644000000000000000000000200600000000000020044 0ustar00"""Tests for typography.exclamation check.""" from proselint.checks.typography import exclamation as chk from .check import Check class TestCheck(Check): """The test class for typography.exclamation.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke_repeated_exclamations(self): """Basic smoke test. Test for typography.exclamation.check_repeated_exclamations. """ assert chk.check_repeated_exclamations( """Smoke phrase with nothing flagged.""") == [] assert chk.check_repeated_exclamations( """I'm really excited!!""") != [] def test_smoke_exclamations_ppm(self): """Basic smoke test. Test for typography.exclamation.check_exclamations_ppm. """ assert chk.check_exclamations_ppm( """Smoke phrase with nothing flagged.""") == [] assert chk.check_exclamations_ppm( """I'm really excited! Really!""") != [] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_typography_symbols.py0000644000000000000000000000353500000000000017240 0ustar00"""Test Butterick's symbols.""" from proselint.checks.typography import symbols as chk from .check import Check class TestCheck(Check): """The test class for typography.symbols.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_ellipsis(self): """Find ... in a string.""" assert chk.check_ellipsis("""The long and winding road...""") def test_copyright(self): """Find a (c) or (C) in a string.""" assert chk.check_copyright_symbol("""Show me the money! (C)""") assert chk.check_copyright_symbol("""Show me the money! (c)""") def test_trademark(self): """Find a (TM) or (tm) in a string.""" assert chk.check_trademark_symbol("""The Fresh Maker (TM)""") assert chk.check_trademark_symbol("""The Fresh Maker (tm)""") def test_registered_trademark(self): """Find a (r) or (R) in a string.""" assert chk.check_registered_trademark_symbol("""Just Do It (R)""") assert chk.check_registered_trademark_symbol("""Just Do It (r)""") def test_sentence_spacing(self): """Find a sentence followed by three or more spaces.""" assert chk.check_sentence_spacing( """This is a sentence. This is another.""") def test_multiplication(self): """Find an x between two digits.""" assert chk.check_multiplication_symbol( """It is obvious that 2 x 2 = 4.""") def test_curly_quotes(self): # FIXME """Find "" quotes in a string.""" assert chk.check_curly_quotes( "\"This should produce an error\", he said.") assert not chk.check_curly_quotes("But this should not.") assert chk.check_curly_quotes("Alas, \"it should here too\".") assert not chk.check_curly_quotes("\"A singular should not, though.") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_uncomparables.py0000644000000000000000000000200400000000000016103 0ustar00"""Test uncomparables.misc""" from proselint.checks.uncomparables import misc as chk from .check import Check class TestCheck(Check): """The test class for uncomparables.misc.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for uncomparables.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""The item was more unique.""") def test_sample_phrases(self): """Find 'very unique'.""" assert not self.passes("""This sentence is very unique.""") def test_spaces(self): """Handle spacing correctly.""" assert not self.passes("""This sentence is very\nunique.""") assert not self.passes("""Kind of complete.""") assert self.passes("""Every perfect instance.""") def test_constitutional(self): """Don't flag exceptions.""" assert self.passes("""A more perfect union.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_weasel_words_misc.py0000644000000000000000000000077100000000000016772 0ustar00"""Tests for weasel_words.misc check.""" from proselint.checks.weasel_words import misc as chk from .check import Check class TestCheck(Check): """The test class for weasel_words.misc.""" __test__ = False @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for weasel_words.misc.""" assert self.passes("""Smoke phrase with nothing flagged.""") # TODO: add test when check is implemented ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250509.1908228 proselint-0.13.0/tests/test_weasel_words_very.py0000644000000000000000000000101300000000000017012 0ustar00"""Tests for weasel_words.very check.""" from proselint.checks.weasel_words import very as chk from .check import Check class TestCheck(Check): """The test class for weasel_words.very.""" __test__ = True @property def this_check(self): """Boilerplate.""" return chk def test_smoke(self): """Basic smoke test for weasel_words.very.""" assert self.passes("""Smoke phrase with nothing flagged.""") assert not self.passes("""The book was very interesting.""") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1636250523.3928878 proselint-0.13.0/setup.py0000644000000000000000000003251500000000000012221 0ustar00# -*- coding: utf-8 -*- from setuptools import setup packages = \ ['proselint', 'proselint.checks', 'proselint.checks.airlinese', 'proselint.checks.annotations', 'proselint.checks.archaism', 'proselint.checks.cliches', 'proselint.checks.consistency', 'proselint.checks.corporate_speak', 'proselint.checks.cursing', 'proselint.checks.dates_times', 'proselint.checks.hedging', 'proselint.checks.hyperbole', 'proselint.checks.inprogress', 'proselint.checks.jargon', 'proselint.checks.lexical_illusions', 'proselint.checks.lgbtq', 'proselint.checks.links', 'proselint.checks.malapropisms', 'proselint.checks.misc', 'proselint.checks.mixed_metaphors', 'proselint.checks.mondegreens', 'proselint.checks.needless_variants', 'proselint.checks.nonwords', 'proselint.checks.oxymorons', 'proselint.checks.psychology', 'proselint.checks.redundancy', 'proselint.checks.security', 'proselint.checks.sexism', 'proselint.checks.skunked_terms', 'proselint.checks.spelling', 'proselint.checks.terms', 'proselint.checks.typography', 'proselint.checks.uncomparables', 'proselint.checks.weasel_words'] package_data = \ {'': ['*']} install_requires = \ ['click>=8.0.0,<9.0.0', 'future>=0.18.2,<0.19.0', 'six>=1.15.0,<2.0.0'] entry_points = \ {'console_scripts': ['proselint = proselint.command_line:proselint']} setup_kwargs = { 'name': 'proselint', 'version': '0.13.0', 'description': 'A linter for prose.', 'long_description': 'proselint logo\n\n![Workflow status](https://github.com/amperser/proselint/actions/workflows/ci-lint-test.yml/badge.svg)\n[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)\n[![Code Climate](https://codeclimate.com/repos/5538989ee30ba0793100090f/badges/e10a2fe18a9256d69e2a/gpa.svg)](https://codeclimate.com/repos/5538989ee30ba0793100090f/feed)\n[![codecov](https://codecov.io/gh/amperser/proselint/branch/main/graph/badge.svg?token=8E0I9sRpot)](https://codecov.io/gh/amperser/proselint)\n[![License](https://img.shields.io/badge/License-BSD-blue.svg)](https://en.wikipedia.org/wiki/BSD_licenses)\n\nWriting is notoriously hard, even for the best writers, and it\'s not for lack of good advice — a tremendous amount of knowledge about the craft is strewn across usage guides, dictionaries, technical manuals, essays, pamphlets, websites, and the hearts and minds of great authors and editors. But poring over Strunk & White hardly makes one a better writer — it turns you into neither Strunk nor White. And nobody has the capacity to apply all the advice from *Garner’s Modern English Usage*, an 1100-page usage guide, to everything they write. In fact, the whole notion that one becomes a better writer by reading advice on writing rests on untenable assumptions about learning and memory. The traditional formats of knowledge about writing are thus essentially inert, waiting to be transformed.\n\nWe devised a simple solution: `proselint`, a linter for English prose. A linter is a computer program that, akin to a spell checker, scans through a file and detects issues — like how a real lint roller helps you get unwanted lint off of your shirt.\n\n`proselint` places the world\'s greatest writers and editors by your side, where they whisper suggestions on how to improve your prose. You’ll be guided by advice inspired by Bryan Garner, David Foster Wallace, Chuck Palahniuk, Steve Pinker, Mary Norris, Mark Twain, Elmore Leonard, George Orwell, Matthew Butterick, William Strunk, Elwyn White, Philip Corbett, Ernest Gowers, and the editorial staff of the world’s finest literary magazines and newspapers, among others. Our goal is to aggregate knowledge about best practices in writing and to make that knowledge immediately accessible to all authors in the form of a linter for prose; all in a neat command-line utility that you can integrate into other tools, scripts, and workflows.\n\n### Installation\n\nTo get this up and running, install it using [pip]:\n\n```bash\npip install proselint\n```\n\n[pip]: https://packaging.python.org/installing/#use-pip-for-installing\n\n#### Fedora\n\n```bash\nsudo dnf install proselint\n```\n\n#### Debian\n\n```bash\nsudo apt install python3-proselint\n```\n\n#### Ubuntu\n\n```bash\nsudo add-apt-repository universe\nsudo apt install python3-proselint\n```\n\n### Plugins for other software\n\n`proselint` is available on:\n\n- A [demo editor](http://proselint.com/write)\n- [Sublime Text](https://github.com/amperser/proselint/tree/main/plugins/sublime/SublimeLinter-contrib-proselint)\n- [Atom Editor](https://github.com/smockle/linter-proselint) (thanks to [Clay Miller](https://github.com/smockle)).\n- [Emacs via Flycheck](http://www.flycheck.org/).\n- Vim via [ALE](https://github.com/w0rp/ale) or [Syntastic](https://github.com/vim-syntastic/syntastic) (thanks to @lcd047, @Carreau, and [Daniel M. Capella](https://github.com/polyzen))\n- [Phabricator\'s `arc` CLI](https://github.com/google/arc-proselint) (thanks to [Jeff Verkoeyen](https://github.com/jverkoey))\n- [Danger](https://github.com/dbgrandi/danger-prose) (thanks to [David Grandinetti](https://github.com/dbgrandi) and [Orta Therox](https://github.com/orta))\n- [Visual Studio Code](https://github.com/ppeszko/vscode-proselint) (thanks to [Patryk Peszko](https://github.com/ppeszko))\n- [coala](https://github.com/coala-analyzer/bear-docs/blob/master/docs/ProseLintBear.rst) (thanks to the [coala Development Group](https://github.com/coala-analyzer))\n- [IntelliJ](https://github.com/kropp/intellij-proselint) (by [Victor Kropp](https://github.com/kropp))\n- [pre-commit](https://pre-commit.com/) (by [Andy Airey](https://github.com/aairey))\n- [Statick](https://github.com/sscpac/statick-md)\n\n### Usage\n\nSuppose you have a document `text.md` with the following text:\n\n```\nJohn is very unique.\n```\n\nYou can run `proselint` over the document using the command line:\n\n```bash\nproselint text.md\n```\n\nThis prints a list of suggestions to stdout, one per line. Each suggestion has the form:\n\n```bash\ntext.md::: \n```\n\nFor example,\n\n```bash\ntext.md:0:10: wallace.uncomparables Comparison of an uncomparable: \'unique\' cannot be compared.\n```\n\nThe command-line utility can also print suggestions in JSON using the `--json` flag. In this case, the output is considerably richer:\n\n```jsonc\n{\n // Type of check that output this suggestion.\n check: "wallace.uncomparables",\n\n // Message to describe the suggestion.\n message: "Comparison of an uncomparable: \'unique\' cannot be compared.",\n\n // The person or organization giving the suggestion.\n source: "David Foster Wallace"\n\n // URL pointing to the source material.\n source_url: "http://www.telegraph.co.uk/a/9715551"\n\n // Line where the error starts.\n line: 0,\n\n // Column where the error starts.\n column: 10,\n\n // Index in the text where the error starts.\n start: 10,\n\n // Index in the text where the error ends.\n end: 21,\n\n // length from start -> end\n extent: 11,\n\n // How important is this? Can be "suggestion", "warning", or "error".\n severity: "warning",\n\n // Possible replacements.\n replacements: [\n {\n value: "unique"\n }\n ]\n}\n```\n\nTo run the linter as part of another Python program, you can use the `lint` function in `proselint.tools`:\n\n```python\nimport proselint\n\nsuggestions = proselint.tools.lint("This sentence is very unique")\n```\n\nThis will return a list of suggestions:\n\n```python\n[(\'weasel_words.very\', "Substitute \'damn\' every time you\'re inclined to write \'very;\' your editor will delete it and the writing will be just as it should be.", 0, 17, 17, 22, 5, \'warning\', None), (\'uncomparables.misc\', "Comparison of an uncomparable: \'very unique.\' is not comparable.", 0, 17, 17, 29, 12, \'warning\', None)]\n```\n\n### Checks\n\nYou can disable any of the checks by modifying `$XDG_CONFIG_HOME/proselint/config`. If `$XDG_CONFIG_HOME` is not set or empty, `~/.config/proselint/config` will be used. Additionally, for compatibility reasons, the legacy configuration `~/.proselintrc` will be checked if `$XDG_CONFIG_HOME/proselint/config` does not exist.\n\n```json\n{\n "checks": {\n "typography.diacritical_marks": false\n }\n}\n```\n\n| ID | Description |\n| ----- | --------------- |\n| `airlinese.misc` | Avoiding jargon of the airline industry |\n| `annotations.misc` | Catching annotations left in the text |\n| `archaism.misc` | Avoiding archaic forms |\n| `cliches.hell` | Avoiding a common cliché |\n| `cliches.misc` | Avoiding clichés |\n| `consistency.spacing` | Consistent sentence spacing |\n| `consistency.spelling` | Consistent spelling |\n| `corporate_speak.misc` | Avoiding corporate buzzwords |\n| `cursing.filth` | Words to avoid |\n| `cursing.nfl` | Avoiding words banned by the NFL |\n| `dates_times.am_pm` | Using the right form for the time of day |\n| `dates_times.dates` | Stylish formatting of dates |\n| `hedging.misc` | Not hedging |\n| `hyperbole.misc` | Not being hyperbolic |\n| `jargon.misc` | Avoiding miscellaneous jargon |\n| `lgbtq.offensive_terms` | Avoding offensive LGBTQ terms |\n| `lgbtq.terms` | Misused LGBTQ terms |\n| `lexical_illusions.misc` | Avoiding lexical illusions |\n| `links.broken` | Linking only to existing sites |\n| `malapropisms.misc` | Avoiding common malapropisms |\n| `misc.apologizing` | Being confident |\n| `misc.back_formations` | Avoiding needless backformations |\n| `misc.bureaucratese` | Avoiding bureaucratese |\n| `misc.but` | Avoid starting a paragraph with "But..." |\n| `misc.capitalization` | Capitalizing only what ought to be capitalized |\n| `misc.chatspeak` | Avoiding lolling and other chatspeak |\n| `misc.commercialese` | Avoiding jargon of the commercial world |\n| `misc.currency` | Avoiding redundant currency symbols |\n| `misc.debased` | Avoiding debased language |\n| `misc.false_plurals` | Avoiding false plurals |\n| `misc.illogic` | Avoiding illogical forms |\n| `misc.inferior_superior` | Superior to, not than |\n| `misc.latin` | Avoiding overuse of Latin phrases |\n| `misc.many_a` | Many a singular |\n| `misc.metaconcepts` | Avoiding overuse of metaconcepts |\n| `misc.narcissism` | Talking about the subject, not its study |\n| `misc.phrasal_adjectives` | Hyphenating phrasal adjectives |\n| `misc.preferred_forms` | Miscellaneous preferred forms |\n| `misc.pretension` | Avoiding being pretentious |\n| `misc.professions` | Calling jobs by the right name |\n| `misc.punctuation` | Using punctuation assiduously |\n| `misc.scare_quotes` | Using scare quotes only when needed |\n| `misc.suddenly` | Avoiding the word suddenly |\n| `misc.tense_present` | Advice from Tense Present |\n| `misc.waxed` | Waxing poetic |\n| `misc.whence` | Using "whence" |\n| `mixed_metaphors.misc` | Not mixing metaphors |\n| `mondegreens.misc` | Avoiding mondegreen |\n| `needless_variants.misc` | Using the preferred form |\n| `nonwords.misc` | Avoid using nonwords |\n| `oxymorons.misc` | Avoiding oxymorons |\n| `psychology.misc` | Avoiding misused psychological terms |\n| `redundancy.misc` | Avoiding redundancy and saying things twice |\n| `redundancy.ras_syndrome` | Avoiding RAS syndrome |\n| `skunked_terms.misc` | Avoid using skunked terms |\n| `spelling.able_atable` | -able vs. -atable |\n| `spelling.able_ible` | -able vs. -ible |\n| `spelling.athletes` | Spelling of athlete names |\n| `spelling.em_im_en_in` | -em vs. -im and -en vs. -in |\n| `spelling.er_or` | -er vs. -or |\n| `spelling.in_un` | in- vs. un- |\n| `spelling.misc` | Spelling words corectly |\n| `security.credit_card` | Keeping credit card numbers secret |\n| `security.password` | Keeping passwords secret |\n| `sexism.misc` | Avoiding sexist language |\n| `terms.animal_adjectives` | Animal adjectives |\n| `terms.denizen_labels` | Calling denizens by the right name |\n| `terms.eponymous_adjectives` | Calling people by the right name |\n| `terms.venery` | Call groups of animals by the right name |\n| `typography.diacritical_marks` | Using dïacríticâl marks |\n| `typography.exclamation` | Avoiding overuse of exclamation |\n| `typography.symbols` | Using the right symbols |\n| `uncomparables.misc` | Not comparing uncomparables |\n| `weasel_words.misc` | Avoiding weasel words |\n| `weasel_words.very` | Avoiding the word "very" |\n\n### Contributing\n\nInterested in contributing to `proselint`? Great — there are plenty of ways you can help. Read more on [our website], where we describe how you can help us build `proselint` into the greatest writing tool in the world.\n\n- [Issue Tracker](http://github.com/amperser/proselint/issues)\n- [Source Code](http://github.com/amperser/proselint)\n\n[our website]: http://proselint.com/contributing/\n\n### Support\n\nIf you run into a problem, please [open an issue](http://github.com/amperser/proselint/issues) in or send an email to hello@amperser.com.\n\n### Running Automated Tests\n\nAutomated tests are included in the `proselint/tests` directory. To run these tests locally, you can use `./utils`.\n\n### License\n\nThe project is licensed under the BSD license.\n', 'author': 'Amperser Labs', 'author_email': 'hello@amperser.com', 'maintainer': None, 'maintainer_email': None, 'url': 'https://proselint.com', 'packages': packages, 'package_data': package_data, 'install_requires': install_requires, 'entry_points': entry_points, 'python_requires': '>=3.6.1,<4.0.0', } setup(**setup_kwargs) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1636250523.393498 proselint-0.13.0/PKG-INFO0000644000000000000000000003010500000000000011575 0ustar00Metadata-Version: 2.1 Name: proselint Version: 0.13.0 Summary: A linter for prose. Home-page: https://proselint.com License: BSD-3-Clause Author: Amperser Labs Author-email: hello@amperser.com Requires-Python: >=3.6.1,<4.0.0 Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Dist: click (>=8.0.0,<9.0.0) Requires-Dist: future (>=0.18.2,<0.19.0) Requires-Dist: six (>=1.15.0,<2.0.0) Project-URL: Repository, https://github.com/amperser/proselint Description-Content-Type: text/markdown proselint logo ![Workflow status](https://github.com/amperser/proselint/actions/workflows/ci-lint-test.yml/badge.svg) [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) [![Code Climate](https://codeclimate.com/repos/5538989ee30ba0793100090f/badges/e10a2fe18a9256d69e2a/gpa.svg)](https://codeclimate.com/repos/5538989ee30ba0793100090f/feed) [![codecov](https://codecov.io/gh/amperser/proselint/branch/main/graph/badge.svg?token=8E0I9sRpot)](https://codecov.io/gh/amperser/proselint) [![License](https://img.shields.io/badge/License-BSD-blue.svg)](https://en.wikipedia.org/wiki/BSD_licenses) Writing is notoriously hard, even for the best writers, and it's not for lack of good advice — a tremendous amount of knowledge about the craft is strewn across usage guides, dictionaries, technical manuals, essays, pamphlets, websites, and the hearts and minds of great authors and editors. But poring over Strunk & White hardly makes one a better writer — it turns you into neither Strunk nor White. And nobody has the capacity to apply all the advice from *Garner’s Modern English Usage*, an 1100-page usage guide, to everything they write. In fact, the whole notion that one becomes a better writer by reading advice on writing rests on untenable assumptions about learning and memory. The traditional formats of knowledge about writing are thus essentially inert, waiting to be transformed. We devised a simple solution: `proselint`, a linter for English prose. A linter is a computer program that, akin to a spell checker, scans through a file and detects issues — like how a real lint roller helps you get unwanted lint off of your shirt. `proselint` places the world's greatest writers and editors by your side, where they whisper suggestions on how to improve your prose. You’ll be guided by advice inspired by Bryan Garner, David Foster Wallace, Chuck Palahniuk, Steve Pinker, Mary Norris, Mark Twain, Elmore Leonard, George Orwell, Matthew Butterick, William Strunk, Elwyn White, Philip Corbett, Ernest Gowers, and the editorial staff of the world’s finest literary magazines and newspapers, among others. Our goal is to aggregate knowledge about best practices in writing and to make that knowledge immediately accessible to all authors in the form of a linter for prose; all in a neat command-line utility that you can integrate into other tools, scripts, and workflows. ### Installation To get this up and running, install it using [pip]: ```bash pip install proselint ``` [pip]: https://packaging.python.org/installing/#use-pip-for-installing #### Fedora ```bash sudo dnf install proselint ``` #### Debian ```bash sudo apt install python3-proselint ``` #### Ubuntu ```bash sudo add-apt-repository universe sudo apt install python3-proselint ``` ### Plugins for other software `proselint` is available on: - A [demo editor](http://proselint.com/write) - [Sublime Text](https://github.com/amperser/proselint/tree/main/plugins/sublime/SublimeLinter-contrib-proselint) - [Atom Editor](https://github.com/smockle/linter-proselint) (thanks to [Clay Miller](https://github.com/smockle)). - [Emacs via Flycheck](http://www.flycheck.org/). - Vim via [ALE](https://github.com/w0rp/ale) or [Syntastic](https://github.com/vim-syntastic/syntastic) (thanks to @lcd047, @Carreau, and [Daniel M. Capella](https://github.com/polyzen)) - [Phabricator's `arc` CLI](https://github.com/google/arc-proselint) (thanks to [Jeff Verkoeyen](https://github.com/jverkoey)) - [Danger](https://github.com/dbgrandi/danger-prose) (thanks to [David Grandinetti](https://github.com/dbgrandi) and [Orta Therox](https://github.com/orta)) - [Visual Studio Code](https://github.com/ppeszko/vscode-proselint) (thanks to [Patryk Peszko](https://github.com/ppeszko)) - [coala](https://github.com/coala-analyzer/bear-docs/blob/master/docs/ProseLintBear.rst) (thanks to the [coala Development Group](https://github.com/coala-analyzer)) - [IntelliJ](https://github.com/kropp/intellij-proselint) (by [Victor Kropp](https://github.com/kropp)) - [pre-commit](https://pre-commit.com/) (by [Andy Airey](https://github.com/aairey)) - [Statick](https://github.com/sscpac/statick-md) ### Usage Suppose you have a document `text.md` with the following text: ``` John is very unique. ``` You can run `proselint` over the document using the command line: ```bash proselint text.md ``` This prints a list of suggestions to stdout, one per line. Each suggestion has the form: ```bash text.md::: ``` For example, ```bash text.md:0:10: wallace.uncomparables Comparison of an uncomparable: 'unique' cannot be compared. ``` The command-line utility can also print suggestions in JSON using the `--json` flag. In this case, the output is considerably richer: ```jsonc { // Type of check that output this suggestion. check: "wallace.uncomparables", // Message to describe the suggestion. message: "Comparison of an uncomparable: 'unique' cannot be compared.", // The person or organization giving the suggestion. source: "David Foster Wallace" // URL pointing to the source material. source_url: "http://www.telegraph.co.uk/a/9715551" // Line where the error starts. line: 0, // Column where the error starts. column: 10, // Index in the text where the error starts. start: 10, // Index in the text where the error ends. end: 21, // length from start -> end extent: 11, // How important is this? Can be "suggestion", "warning", or "error". severity: "warning", // Possible replacements. replacements: [ { value: "unique" } ] } ``` To run the linter as part of another Python program, you can use the `lint` function in `proselint.tools`: ```python import proselint suggestions = proselint.tools.lint("This sentence is very unique") ``` This will return a list of suggestions: ```python [('weasel_words.very', "Substitute 'damn' every time you're inclined to write 'very;' your editor will delete it and the writing will be just as it should be.", 0, 17, 17, 22, 5, 'warning', None), ('uncomparables.misc', "Comparison of an uncomparable: 'very unique.' is not comparable.", 0, 17, 17, 29, 12, 'warning', None)] ``` ### Checks You can disable any of the checks by modifying `$XDG_CONFIG_HOME/proselint/config`. If `$XDG_CONFIG_HOME` is not set or empty, `~/.config/proselint/config` will be used. Additionally, for compatibility reasons, the legacy configuration `~/.proselintrc` will be checked if `$XDG_CONFIG_HOME/proselint/config` does not exist. ```json { "checks": { "typography.diacritical_marks": false } } ``` | ID | Description | | ----- | --------------- | | `airlinese.misc` | Avoiding jargon of the airline industry | | `annotations.misc` | Catching annotations left in the text | | `archaism.misc` | Avoiding archaic forms | | `cliches.hell` | Avoiding a common cliché | | `cliches.misc` | Avoiding clichés | | `consistency.spacing` | Consistent sentence spacing | | `consistency.spelling` | Consistent spelling | | `corporate_speak.misc` | Avoiding corporate buzzwords | | `cursing.filth` | Words to avoid | | `cursing.nfl` | Avoiding words banned by the NFL | | `dates_times.am_pm` | Using the right form for the time of day | | `dates_times.dates` | Stylish formatting of dates | | `hedging.misc` | Not hedging | | `hyperbole.misc` | Not being hyperbolic | | `jargon.misc` | Avoiding miscellaneous jargon | | `lgbtq.offensive_terms` | Avoding offensive LGBTQ terms | | `lgbtq.terms` | Misused LGBTQ terms | | `lexical_illusions.misc` | Avoiding lexical illusions | | `links.broken` | Linking only to existing sites | | `malapropisms.misc` | Avoiding common malapropisms | | `misc.apologizing` | Being confident | | `misc.back_formations` | Avoiding needless backformations | | `misc.bureaucratese` | Avoiding bureaucratese | | `misc.but` | Avoid starting a paragraph with "But..." | | `misc.capitalization` | Capitalizing only what ought to be capitalized | | `misc.chatspeak` | Avoiding lolling and other chatspeak | | `misc.commercialese` | Avoiding jargon of the commercial world | | `misc.currency` | Avoiding redundant currency symbols | | `misc.debased` | Avoiding debased language | | `misc.false_plurals` | Avoiding false plurals | | `misc.illogic` | Avoiding illogical forms | | `misc.inferior_superior` | Superior to, not than | | `misc.latin` | Avoiding overuse of Latin phrases | | `misc.many_a` | Many a singular | | `misc.metaconcepts` | Avoiding overuse of metaconcepts | | `misc.narcissism` | Talking about the subject, not its study | | `misc.phrasal_adjectives` | Hyphenating phrasal adjectives | | `misc.preferred_forms` | Miscellaneous preferred forms | | `misc.pretension` | Avoiding being pretentious | | `misc.professions` | Calling jobs by the right name | | `misc.punctuation` | Using punctuation assiduously | | `misc.scare_quotes` | Using scare quotes only when needed | | `misc.suddenly` | Avoiding the word suddenly | | `misc.tense_present` | Advice from Tense Present | | `misc.waxed` | Waxing poetic | | `misc.whence` | Using "whence" | | `mixed_metaphors.misc` | Not mixing metaphors | | `mondegreens.misc` | Avoiding mondegreen | | `needless_variants.misc` | Using the preferred form | | `nonwords.misc` | Avoid using nonwords | | `oxymorons.misc` | Avoiding oxymorons | | `psychology.misc` | Avoiding misused psychological terms | | `redundancy.misc` | Avoiding redundancy and saying things twice | | `redundancy.ras_syndrome` | Avoiding RAS syndrome | | `skunked_terms.misc` | Avoid using skunked terms | | `spelling.able_atable` | -able vs. -atable | | `spelling.able_ible` | -able vs. -ible | | `spelling.athletes` | Spelling of athlete names | | `spelling.em_im_en_in` | -em vs. -im and -en vs. -in | | `spelling.er_or` | -er vs. -or | | `spelling.in_un` | in- vs. un- | | `spelling.misc` | Spelling words corectly | | `security.credit_card` | Keeping credit card numbers secret | | `security.password` | Keeping passwords secret | | `sexism.misc` | Avoiding sexist language | | `terms.animal_adjectives` | Animal adjectives | | `terms.denizen_labels` | Calling denizens by the right name | | `terms.eponymous_adjectives` | Calling people by the right name | | `terms.venery` | Call groups of animals by the right name | | `typography.diacritical_marks` | Using dïacríticâl marks | | `typography.exclamation` | Avoiding overuse of exclamation | | `typography.symbols` | Using the right symbols | | `uncomparables.misc` | Not comparing uncomparables | | `weasel_words.misc` | Avoiding weasel words | | `weasel_words.very` | Avoiding the word "very" | ### Contributing Interested in contributing to `proselint`? Great — there are plenty of ways you can help. Read more on [our website], where we describe how you can help us build `proselint` into the greatest writing tool in the world. - [Issue Tracker](http://github.com/amperser/proselint/issues) - [Source Code](http://github.com/amperser/proselint) [our website]: http://proselint.com/contributing/ ### Support If you run into a problem, please [open an issue](http://github.com/amperser/proselint/issues) in or send an email to hello@amperser.com. ### Running Automated Tests Automated tests are included in the `proselint/tests` directory. To run these tests locally, you can use `./utils`. ### License The project is licensed under the BSD license.