Skip to content

Commit 022d513

Browse files
committed
Add initial code
1 parent 9255d8b commit 022d513

File tree

10 files changed

+194
-2
lines changed

10 files changed

+194
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,6 @@ ENV/
9999

100100
# mypy
101101
.mypy_cache/
102+
103+
# PyTest
104+
.pytest_cache/

.relint.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- name: No ToDo
2+
pattern: "[tT][oO][dD][oO]"
3+
hint: Get it done right away!
4+
filename:
5+
- "*.py"
6+
- "*.js"

.travis.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
language: python
2+
python:
3+
- '3.5'
4+
- '3.6'
5+
script: relint **
6+
deploy:
7+
provider: pypi
8+
user: codingjoe
9+
password:
10+
secure: ocotP5Wb+UCtsqMAXOuzCKUabQ6pVkhvRRdNMsJ+MX+Fc6O6fLW90rD5Ny03z29HdvNfedty7hzhGYVdGwOUdMeSy36od1wN7PFZ6cI7Uotn+YBvl249kNdNQFkVcowywUjYi7tuzbl/0VVi1zFCHJvw7Dv0N5LNv7aHeJxuv/9CmOcA+7mEauDjse7zpYxIOO33XLrg2eAZrL1GPZTzrEA7urKBPO6w0s9Z6EUTkEpRYZPBqS6ajCWONNK9Dj65i2GVhEp8SEoAG3ILCiLbM45nJYoUBeL20yG6TliXis4P51JalcgYtq1Ir41vprODA5dWVzgaeS0lemU19ym/Gs+32EmIeMQuJ0QpylmqJ6OjMLTU0QK4up9Eydr824wZ8Q2iZ8zUkaKvrIsLinQ/BSaSnQmz21X0mMXQdDLbI+VmwEOYqaa6liQOK1j1bXSOaCFu6fVV7AiktbCcUeg5NX259VT5+yuAJ49cw3fY7P3pu8+vviNkyKK7APbvhaK4eo4yvGCHGeWaoYrJkb+Vz9yWRathe5nSWzAgOl+fu6N8QCVljMvj1GTt+eVfH8Rr0re7/e/6dv2MiAec+w3K40gp/3J6axMMGfxEQy37+3HCUsgmAGJSFs6uj6KX+HfZTBZIAqyo+KavVos/Ml9zvyf8nTw70FGpay8btc5fyqU=
11+
on:
12+
tags: true
13+
distributions: sdist bdist_wheel
14+
repo: codingjoe/relint

README.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

README.rst

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
ReLint
2+
======
3+
4+
**Regular Expression Linter**
5+
6+
*Write your own linting rules using regular expressions.*
7+
8+
Usage
9+
-----
10+
11+
You can write your own regular rules in a YAML file, like so:
12+
13+
.. code-block:: YAML
14+
15+
- name: No ToDo
16+
pattern: "[tT][oO][dD][oO]"
17+
hint: Get it done right away!
18+
filename:
19+
- "*.py"
20+
- "*.js"
21+
22+
The ``name`` attribute is the name of your linter, the ``pattern`` can be
23+
any regular expression. The linter does lint entire files, therefore your
24+
expressions can match multiple lines and include newlines.
25+
26+
You can narrow down the file types your linter should be working with, by
27+
providing the optional ``filename`` attribute. The default is ``*``.
28+
29+
The following command will lint all files in the current directory:
30+
31+
.. code-block:: bash
32+
33+
relint -c .relint.yml **
34+
35+
The default configuration file name is `.relint.yaml` within your working
36+
directory, but you can provide any YAML or JSON file.

relint.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import argparse
2+
import fnmatch
3+
import glob
4+
import re
5+
from collections import namedtuple
6+
from itertools import chain
7+
8+
import yaml
9+
10+
11+
def parse_args():
12+
parser = argparse.ArgumentParser()
13+
parser.add_argument(
14+
'files',
15+
metavar='FILE',
16+
type=str,
17+
nargs='+',
18+
help='Path to one or multiple files to be checked.'
19+
)
20+
parser.add_argument(
21+
'--config',
22+
'-c',
23+
metavar='CONFIG_FILE',
24+
type=str,
25+
default='.relint.yml',
26+
help='Path to config file, default: .relint.yml'
27+
)
28+
return parser.parse_args()
29+
30+
31+
Test = namedtuple('Test', ('name', 'pattern', 'hint', 'filename'))
32+
33+
34+
def load_config(path):
35+
with open(path) as fs:
36+
for test in yaml.load(fs):
37+
filename = test.get('filename', ['*'])
38+
if not isinstance(filename, list):
39+
filename = list(filename)
40+
yield Test(
41+
name=test['name'],
42+
pattern=re.compile(test['pattern']),
43+
hint=test.get('hint'),
44+
filename=filename,
45+
)
46+
47+
48+
def lint_file(filename, tests):
49+
try:
50+
with open(filename) as fs:
51+
content = fs.read()
52+
except (IsADirectoryError, UnicodeDecodeError):
53+
pass
54+
else:
55+
for test in tests:
56+
if any(fnmatch.fnmatch(filename, fp) for fp in test.filename):
57+
for match in test.pattern.finditer(content):
58+
yield filename, test, match
59+
60+
61+
def main():
62+
args = parse_args()
63+
paths = {
64+
path
65+
for file in args.files
66+
for path in glob.iglob(file, recursive=True)
67+
}
68+
69+
tests = list(load_config(args.config))
70+
71+
matches = chain.from_iterable(
72+
lint_file(path, tests)
73+
for path in paths
74+
)
75+
76+
_filename = ''
77+
lines = []
78+
79+
for filename, test, match in matches:
80+
if filename != _filename:
81+
_filename = filename
82+
lines = match.string.splitlines()
83+
84+
line_no = match.string[:match.start()].count('\n')
85+
print(f"{filename}:{line_no + 1} {test.name}")
86+
if test.hint:
87+
print("Hint:", test.hint)
88+
print("> ", lines[line_no])
89+
90+
91+
if __name__ == '__main__':
92+
main()

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PyYAML

setup.cfg

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[metadata]
2+
name = lintipy
3+
author = Johannes Hoppe
4+
author-email = [email protected]
5+
summary = Write your own linting rules using regular expressions
6+
description-file = README.rst
7+
home-page = https://github.com/codingjoe/relint
8+
license = MIT
9+
classifier =
10+
Development Status :: 4 - Beta
11+
Environment :: Console
12+
Intended Audience :: Developers
13+
Intended Audience :: Information Technology
14+
Operating System :: OS Independent
15+
Programming Language :: Python
16+
keywords =
17+
linter
18+
regex
19+
20+
[entry_points]
21+
console_scripts =
22+
relint = relint:main
23+
24+
[pbr]
25+
skip_authors = true
26+
skip_changelog = true
27+
28+
[pycodestyle]
29+
max_line_length = 99
30+
31+
[pydocstyle]
32+
add_ignore = D1

setup.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env python
2+
3+
from setuptools import setup
4+
5+
setup(
6+
setup_requires=['pbr'],
7+
py_modules=['relint'],
8+
pbr=True,
9+
)

test_relint.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# TODO: I'll do it later, promise

0 commit comments

Comments
 (0)