diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..9561fb1 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.rst diff --git a/README.md b/README.md deleted file mode 100644 index 8e96bd5..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -doc8 -==== diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..39161d5 --- /dev/null +++ b/README.rst @@ -0,0 +1,19 @@ +==== +Doc8 +==== + +Doc8 is a *opinionated* style checker for sphinx (or other) `rst`_ +documentation. + +QuickStart +========== + +:: + + pip install doc8 + +To run doc8 just invoke it against any doc directory:: + + $ doc8 coolproject/docs + +.. _rst: http://docutils.sourceforge.net/docs/ref/rst/introduction.html \ No newline at end of file diff --git a/scripts/doc8 b/scripts/doc8 new file mode 100755 index 0000000..d7943b3 --- /dev/null +++ b/scripts/doc8 @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (C) 2014 Ivan Melnikov +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +"""Check documentation for simple style requirements. + +What is checked: + - lines should not be longer than 79 characters + - exception: line with no whitespace except maybe in the beginning + - exception: line that starts with '..' -- longer directives are allowed, + including footnotes + - no tabulation for indentation + - no trailing whitespace +""" + +import fnmatch +import functools +import os +import re +import sys + +from six.moves import configparser + + +FILE_PATTERNS = ['*.rst', '*.txt'] +MAX_LINE_LENGTH = 79 +TRAILING_WHITESPACE_REGEX = re.compile('\s$') +STARTING_WHITESPACE_REGEX = re.compile('^(\s+)') + + +def check_max_length(max_line_length, line): + if len(line) > max_line_length: + stripped = line.strip() + if not any(( + line.startswith('..'), # this is directive + stripped.startswith('>>>'), # this is doctest + stripped.startswith('...'), # and this + stripped.startswith('taskflow.'), + ' ' not in stripped # line can't be split + )): + yield ('D001', 'Line too long') + + +def check_trailing_whitespace(line): + if TRAILING_WHITESPACE_REGEX.search(line): + yield ('D002', 'Trailing whitespace') + + +def check_indentation_no_tab(line): + match = STARTING_WHITESPACE_REGEX.search(line) + if match: + spaces = match.group(1) + if '\t' in spaces: + yield ('D003', 'Tabulation used for indentation') + + +def check_lines(lines, line_checks): + for idx, line in enumerate(lines, 1): + line = line.rstrip('\n') + for check in line_checks: + for code, message in check(line): + yield idx, code, message + + +def check_files(filenames, line_checks): + for fn in filenames: + with open(fn) as f: + for line_num, code, message in check_lines(f, line_checks): + yield fn, line_num, code, message + + +def find_files(pathes, patterns): + for path in pathes: + if os.path.isfile(path): + yield path + elif os.path.isdir(path): + for root, dirnames, filenames in os.walk(path): + for filename in filenames: + if any(fnmatch.fnmatch(filename, pattern) + for pattern in patterns): + yield os.path.join(root, filename) + else: + print('Invalid path: %s' % path) + + +def extract_config(): + filenames = [ + os.path.join(os.getcwd(), "doc8.ini"), + os.path.join(os.getcwd(), "tox.ini"), + os.path.join(os.getcwd(), "pep8.ini"), + ] + parser = configparser.RawConfigParser() + parser.read(filenames) + cfg = {} + try: + cfg['max_line_length'] = parser.getint("doc8", "max_line_length") + except (configparser.NoSectionError, configparser.NoOptionError): + cfg['max_line_length'] = MAX_LINE_LENGTH + try: + ignores = parser.get("doc8", "ignore") + except (configparser.NoSectionError, configparser.NoOptionError): + cfg['ignore'] = set() + else: + ignoreables = set() + for i in ignores.split(","): + i = i.strip() + if not i: + continue + ignoreables.add(i) + cfg['ignore'] = ignoreables + return cfg + + +def main(): + ok = True + if len(sys.argv) > 1: + dirs = sys.argv[1:] + else: + dirs = ['.'] + cfg = extract_config() + line_checks = [ + functools.partial(check_max_length, cfg['max_line_length']), + check_trailing_whitespace, + check_indentation_no_tab, + ] + for error in check_files(find_files(dirs, FILE_PATTERNS), line_checks): + if error[2] in cfg['ignore']: + continue + ok = False + print('%s:%s: %s %s' % error) + sys.exit(0 if ok else 1) + +if __name__ == '__main__': + main() diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..27fa3b5 --- /dev/null +++ b/setup.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +# -*- coding: utf-8 -*- + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (C) 2014 Yahoo! Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +from setuptools import setup + + +def _path(fn): + return os.path.join(os.path.dirname(__file__), fn) + + +def _readme(): + with open(_path("README.rst"), "rb") as handle: + return handle.read() + + +setup(name='doc8', + version='0.1', + description='style checker for sphinx (or other) rst documentation.', + url='https://github.com/harlowja/doc8', + scripts=[ + _path(os.path.join('scripts', 'doc8')), + ], + license="ASL 2.0", + install_requires=[ + 'six', + ], + classifiers=[ + "Development Status :: 3 - Alpha", + "Topic :: Utilities", + "License :: OSI Approved :: Apache Software License", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + ], + keywords="rst docs style checking", + long_description=_readme(), + ) \ No newline at end of file