Project Migration to PyCQA
This change rehomes the project to PyCQA [1] as reported to the openstack-dev mailing list [2]. [1] https://mail.python.org/pipermail/code-quality/2019-July/001112.html [2] http://lists.openstack.org/pipermail/openstack-discuss/2019-July/007878.html Change-Id: I5472bea4994664495220ee4ab565f95666f21eee Signed-off-by: Stephen Finucane <stephenfin@redhat.com> Depends-On: I740c66cd959db1efa8ca8c9f29d4da4b66fbd993
This commit is contained in:
parent
88d50ec108
commit
57d1e9afc9
54
.gitignore
vendored
54
.gitignore
vendored
@ -1,54 +0,0 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
env/
|
|
||||||
bin/
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.coverage
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
|
|
||||||
# Mr Developer
|
|
||||||
.mr.developer.cfg
|
|
||||||
.project
|
|
||||||
.pydevproject
|
|
||||||
|
|
||||||
# Rope
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
doc/build/
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
[gerrit]
|
[gerrit]
|
||||||
host=review.openstack.org
|
host=review.opendev.org
|
||||||
port=29418
|
port=29418
|
||||||
project=openstack/doc8.git
|
project=x/doc8.git
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
If you would like to contribute to the development of OpenStack,
|
|
||||||
you must follow the steps in this page:
|
|
||||||
|
|
||||||
http://docs.openstack.org/infra/manual/developers.html
|
|
||||||
|
|
||||||
Once those steps have been completed, changes to OpenStack
|
|
||||||
should be submitted for review via the Gerrit tool, following
|
|
||||||
the workflow documented at:
|
|
||||||
|
|
||||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
|
||||||
|
|
||||||
Pull requests submitted through GitHub will be ignored.
|
|
||||||
You can report the bugs at launchpad.
|
|
||||||
|
|
||||||
https://bugs.launchpad.net/doc8
|
|
@ -1,4 +0,0 @@
|
|||||||
doc8 Style Commandments
|
|
||||||
===============================================
|
|
||||||
|
|
||||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
|
201
LICENSE
201
LICENSE
@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright {yyyy} {name of copyright owner}
|
|
||||||
|
|
||||||
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.
|
|
@ -1,6 +0,0 @@
|
|||||||
include README.rst
|
|
||||||
|
|
||||||
exclude .gitignore
|
|
||||||
exclude .gitreview
|
|
||||||
|
|
||||||
global-exclude *.pyc
|
|
137
README.rst
137
README.rst
@ -1,135 +1,14 @@
|
|||||||
====
|
====
|
||||||
Doc8
|
doc8
|
||||||
====
|
====
|
||||||
|
|
||||||
Doc8 is an *opinionated* style checker for `rst`_ (with basic support for
|
This project is no longer maintained in OpenStack.
|
||||||
plain text) styles of documentation.
|
|
||||||
|
|
||||||
QuickStart
|
Please visit PyCQA to raise issues or make contributions:
|
||||||
==========
|
|
||||||
|
|
||||||
::
|
https://github.com/PyCQA/doc8
|
||||||
|
|
||||||
pip install doc8
|
The contents of this repository are still available in the Git
|
||||||
|
source code management system. To see the contents of this
|
||||||
To run doc8 just invoke it against any doc directory::
|
repository before it reached its end of life, please check out the
|
||||||
|
previous commit with "git checkout HEAD^1".
|
||||||
$ doc8 coolproject/docs
|
|
||||||
|
|
||||||
Usage
|
|
||||||
=====
|
|
||||||
|
|
||||||
Command line usage
|
|
||||||
******************
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
$ doc8 -h
|
|
||||||
|
|
||||||
usage: doc8 [-h] [--config path] [--allow-long-titles] [--ignore code]
|
|
||||||
[--no-sphinx] [--ignore-path path] [--ignore-path-errors path]
|
|
||||||
[--default-extension extension] [--file-encoding encoding]
|
|
||||||
[--max-line-length int] [-e extension] [-v] [--version]
|
|
||||||
[path [path ...]]
|
|
||||||
|
|
||||||
Check documentation for simple style requirements.
|
|
||||||
|
|
||||||
What is checked:
|
|
||||||
- invalid rst format - D000
|
|
||||||
- lines should not be longer than 79 characters - D001
|
|
||||||
- RST exception: line with no whitespace except in the beginning
|
|
||||||
- RST exception: lines with http or https urls
|
|
||||||
- RST exception: literal blocks
|
|
||||||
- RST exception: rst target directives
|
|
||||||
- no trailing whitespace - D002
|
|
||||||
- no tabulation for indentation - D003
|
|
||||||
- no carriage returns (use unix newlines) - D004
|
|
||||||
- no newline at end of file - D005
|
|
||||||
|
|
||||||
positional arguments:
|
|
||||||
path Path to scan for doc files (default: current
|
|
||||||
directory).
|
|
||||||
|
|
||||||
optional arguments:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
--config path user config file location (default: doc8.ini, tox.ini,
|
|
||||||
pep8.ini, setup.cfg).
|
|
||||||
--allow-long-titles allow long section titles (default: false).
|
|
||||||
--ignore code ignore the given error code(s).
|
|
||||||
--no-sphinx do not ignore sphinx specific false positives.
|
|
||||||
--ignore-path path ignore the given directory or file (globs are
|
|
||||||
supported).
|
|
||||||
--ignore-path-errors path
|
|
||||||
ignore the given specific errors in the provided file.
|
|
||||||
--default-extension extension
|
|
||||||
default file extension to use when a file is found
|
|
||||||
without a file extension.
|
|
||||||
--file-encoding encoding
|
|
||||||
override encoding to use when attempting to determine
|
|
||||||
an input files text encoding (providing this avoids
|
|
||||||
using `chardet` to automatically detect encoding/s)
|
|
||||||
--max-line-length int
|
|
||||||
maximum allowed line length (default: 79).
|
|
||||||
-e extension, --extension extension
|
|
||||||
check file extensions of the given type (default:
|
|
||||||
.rst, .txt).
|
|
||||||
-q, --quiet only print violations
|
|
||||||
-v, --verbose run in verbose mode.
|
|
||||||
--version show the version and exit.
|
|
||||||
|
|
||||||
Ini file usage
|
|
||||||
**************
|
|
||||||
|
|
||||||
Instead of using the CLI for options the following files will also be examined
|
|
||||||
for ``[doc8]`` sections that can also provided the same set of options. If
|
|
||||||
the ``--config path`` option is used these files will **not** be scanned for
|
|
||||||
the current working directory and that configuration path will be used
|
|
||||||
instead.
|
|
||||||
|
|
||||||
* ``$CWD/doc8.ini``
|
|
||||||
* ``$CWD/tox.ini``
|
|
||||||
* ``$CWD/pep8.ini``
|
|
||||||
* ``$CWD/setup.cfg``
|
|
||||||
|
|
||||||
An example section that can be placed into one of these files::
|
|
||||||
|
|
||||||
[doc8]
|
|
||||||
|
|
||||||
ignore-path=/tmp/stuff,/tmp/other_stuff
|
|
||||||
max-line-length=99
|
|
||||||
verbose=1
|
|
||||||
ignore-path-errors=/tmp/other_thing.rst;D001;D002
|
|
||||||
|
|
||||||
**Note:** The option names are the same as the command line ones (with the
|
|
||||||
only variation of this being the ``no-sphinx`` option which from
|
|
||||||
configuration file will be ``sphinx`` instead).
|
|
||||||
|
|
||||||
Option conflict resolution
|
|
||||||
**************************
|
|
||||||
|
|
||||||
When the same option is passed on the command line and also via configuration
|
|
||||||
files the following strategies are applied to resolve these types
|
|
||||||
of conflicts.
|
|
||||||
|
|
||||||
====================== =========== ========
|
|
||||||
Option Overrides Merges
|
|
||||||
====================== =========== ========
|
|
||||||
``allow-long-titles`` Yes No
|
|
||||||
``ignore-path-errors`` No Yes
|
|
||||||
``default-extension`` Yes No
|
|
||||||
``extension`` No Yes
|
|
||||||
``ignore-path`` No Yes
|
|
||||||
``ignore`` No Yes
|
|
||||||
``max-line-length`` Yes No
|
|
||||||
``file-encoding`` Yes No
|
|
||||||
``sphinx`` Yes No
|
|
||||||
====================== =========== ========
|
|
||||||
|
|
||||||
**Note:** In the above table the configuration file option when specified as
|
|
||||||
*overrides* will replace the same option given via the command line. When
|
|
||||||
*merges* is stated then the option will be combined with the command line
|
|
||||||
option (for example by becoming a larger list or set of values that contains
|
|
||||||
the values passed on the command line *and* the values passed via
|
|
||||||
configuration).
|
|
||||||
|
|
||||||
.. _rst: http://docutils.sourceforge.net/docs/ref/rst/introduction.html
|
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
# 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
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath('../..'))
|
|
||||||
# -- General configuration ----------------------------------------------------
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
|
||||||
extensions = [
|
|
||||||
'sphinx.ext.autodoc',
|
|
||||||
'oslosphinx'
|
|
||||||
]
|
|
||||||
|
|
||||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
|
||||||
# text edit cycles.
|
|
||||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'doc8'
|
|
||||||
copyright = u'2013, OpenStack Foundation'
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
add_module_names = True
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# -- Options for HTML output --------------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
|
||||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
|
||||||
# html_theme_path = ["."]
|
|
||||||
# html_theme = '_theme'
|
|
||||||
# html_static_path = ['static']
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = '%sdoc' % project
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title, author, documentclass
|
|
||||||
# [howto/manual]).
|
|
||||||
latex_documents = [
|
|
||||||
('index',
|
|
||||||
'%s.tex' % project,
|
|
||||||
u'%s Documentation' % project,
|
|
||||||
u'OpenStack Foundation', 'manual'),
|
|
||||||
]
|
|
@ -1,4 +0,0 @@
|
|||||||
============
|
|
||||||
Contributing
|
|
||||||
============
|
|
||||||
.. include:: ../../CONTRIBUTING.rst
|
|
@ -1,20 +0,0 @@
|
|||||||
Welcome to doc8's documentation!
|
|
||||||
================================
|
|
||||||
|
|
||||||
Contents:
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
readme
|
|
||||||
installation
|
|
||||||
usage
|
|
||||||
contributing
|
|
||||||
|
|
||||||
Indices and tables
|
|
||||||
==================
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`modindex`
|
|
||||||
* :ref:`search`
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
============
|
|
||||||
Installation
|
|
||||||
============
|
|
||||||
|
|
||||||
At the command line::
|
|
||||||
|
|
||||||
$ pip install doc8
|
|
||||||
|
|
||||||
Or, if you have virtualenvwrapper installed::
|
|
||||||
|
|
||||||
$ mkvirtualenv doc8
|
|
||||||
$ pip install doc8
|
|
@ -1 +0,0 @@
|
|||||||
.. include:: ../../README.rst
|
|
@ -1,7 +0,0 @@
|
|||||||
=====
|
|
||||||
Usage
|
|
||||||
=====
|
|
||||||
|
|
||||||
To use doc8 in a project::
|
|
||||||
|
|
||||||
import doc8
|
|
302
doc8/checks.py
302
doc8/checks.py
@ -1,302 +0,0 @@
|
|||||||
# Copyright (C) 2014 Ivan Melnikov <iv at altlinux dot org>
|
|
||||||
#
|
|
||||||
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
|
|
||||||
#
|
|
||||||
# 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 abc
|
|
||||||
import collections
|
|
||||||
import re
|
|
||||||
|
|
||||||
from docutils import nodes as docutils_nodes
|
|
||||||
import six
|
|
||||||
|
|
||||||
from doc8 import utils
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class ContentCheck(object):
|
|
||||||
def __init__(self, cfg):
|
|
||||||
self._cfg = cfg
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def report_iter(self, parsed_file):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class LineCheck(object):
|
|
||||||
def __init__(self, cfg):
|
|
||||||
self._cfg = cfg
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def report_iter(self, line):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CheckTrailingWhitespace(LineCheck):
|
|
||||||
_TRAILING_WHITESPACE_REGEX = re.compile('\s$')
|
|
||||||
REPORTS = frozenset(["D002"])
|
|
||||||
|
|
||||||
def report_iter(self, line):
|
|
||||||
if self._TRAILING_WHITESPACE_REGEX.search(line):
|
|
||||||
yield ('D002', 'Trailing whitespace')
|
|
||||||
|
|
||||||
|
|
||||||
class CheckIndentationNoTab(LineCheck):
|
|
||||||
_STARTING_WHITESPACE_REGEX = re.compile('^(\s+)')
|
|
||||||
REPORTS = frozenset(["D003"])
|
|
||||||
|
|
||||||
def report_iter(self, line):
|
|
||||||
match = self._STARTING_WHITESPACE_REGEX.search(line)
|
|
||||||
if match:
|
|
||||||
spaces = match.group(1)
|
|
||||||
if '\t' in spaces:
|
|
||||||
yield ('D003', 'Tabulation used for indentation')
|
|
||||||
|
|
||||||
|
|
||||||
class CheckCarriageReturn(LineCheck):
|
|
||||||
REPORTS = frozenset(["D004"])
|
|
||||||
|
|
||||||
def report_iter(self, line):
|
|
||||||
if "\r" in line:
|
|
||||||
yield ('D004', 'Found literal carriage return')
|
|
||||||
|
|
||||||
|
|
||||||
class CheckNewlineEndOfFile(ContentCheck):
|
|
||||||
REPORTS = frozenset(["D005"])
|
|
||||||
|
|
||||||
def __init__(self, cfg):
|
|
||||||
super(CheckNewlineEndOfFile, self).__init__(cfg)
|
|
||||||
|
|
||||||
def report_iter(self, parsed_file):
|
|
||||||
if parsed_file.lines and not parsed_file.lines[-1].endswith(b'\n'):
|
|
||||||
yield (len(parsed_file.lines), 'D005', 'No newline at end of file')
|
|
||||||
|
|
||||||
|
|
||||||
class CheckValidity(ContentCheck):
|
|
||||||
REPORTS = frozenset(["D000"])
|
|
||||||
EXT_MATCHER = re.compile(r"(.*)[.]rst", re.I)
|
|
||||||
|
|
||||||
# From docutils docs:
|
|
||||||
#
|
|
||||||
# Report system messages at or higher than <level>: "info" or "1",
|
|
||||||
# "warning"/"2" (default), "error"/"3", "severe"/"4", "none"/"5"
|
|
||||||
#
|
|
||||||
# See: http://docutils.sourceforge.net/docs/user/config.html#report-level
|
|
||||||
WARN_LEVELS = frozenset([2, 3, 4])
|
|
||||||
|
|
||||||
# Only used when running in sphinx mode.
|
|
||||||
SPHINX_IGNORES_REGEX = [
|
|
||||||
re.compile(r'^Unknown interpreted text'),
|
|
||||||
re.compile(r'^Unknown directive type'),
|
|
||||||
re.compile(r'^Undefined substitution'),
|
|
||||||
re.compile(r'^Substitution definition contains illegal element'),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, cfg):
|
|
||||||
super(CheckValidity, self).__init__(cfg)
|
|
||||||
self._sphinx_mode = cfg.get('sphinx')
|
|
||||||
|
|
||||||
def report_iter(self, parsed_file):
|
|
||||||
for error in parsed_file.errors:
|
|
||||||
if error.level not in self.WARN_LEVELS:
|
|
||||||
continue
|
|
||||||
ignore = False
|
|
||||||
if self._sphinx_mode:
|
|
||||||
for m in self.SPHINX_IGNORES_REGEX:
|
|
||||||
if m.match(error.message):
|
|
||||||
ignore = True
|
|
||||||
break
|
|
||||||
if not ignore:
|
|
||||||
yield (error.line, 'D000', error.message)
|
|
||||||
|
|
||||||
|
|
||||||
class CheckMaxLineLength(ContentCheck):
|
|
||||||
REPORTS = frozenset(["D001"])
|
|
||||||
|
|
||||||
def __init__(self, cfg):
|
|
||||||
super(CheckMaxLineLength, self).__init__(cfg)
|
|
||||||
self._max_line_length = self._cfg['max_line_length']
|
|
||||||
self._allow_long_titles = self._cfg['allow_long_titles']
|
|
||||||
|
|
||||||
def _extract_node_lines(self, doc):
|
|
||||||
|
|
||||||
def extract_lines(node, start_line):
|
|
||||||
lines = [start_line]
|
|
||||||
if isinstance(node, (docutils_nodes.title)):
|
|
||||||
start = start_line - len(node.rawsource.splitlines())
|
|
||||||
if start >= 0:
|
|
||||||
lines.append(start)
|
|
||||||
if isinstance(node, (docutils_nodes.literal_block)):
|
|
||||||
end = start_line + len(node.rawsource.splitlines()) - 1
|
|
||||||
lines.append(end)
|
|
||||||
return lines
|
|
||||||
|
|
||||||
def gather_lines(node):
|
|
||||||
lines = []
|
|
||||||
for n in node.traverse(include_self=True):
|
|
||||||
lines.extend(extract_lines(n, find_line(n)))
|
|
||||||
return lines
|
|
||||||
|
|
||||||
def find_line(node):
|
|
||||||
n = node
|
|
||||||
while n is not None:
|
|
||||||
if n.line is not None:
|
|
||||||
return n.line
|
|
||||||
n = n.parent
|
|
||||||
return None
|
|
||||||
|
|
||||||
def filter_systems(node):
|
|
||||||
if utils.has_any_node_type(node, (docutils_nodes.system_message,)):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
nodes_lines = []
|
|
||||||
first_line = -1
|
|
||||||
for n in utils.filtered_traverse(doc, filter_systems):
|
|
||||||
line = find_line(n)
|
|
||||||
if line is None:
|
|
||||||
continue
|
|
||||||
if first_line == -1:
|
|
||||||
first_line = line
|
|
||||||
contained_lines = set(gather_lines(n))
|
|
||||||
nodes_lines.append((n, (min(contained_lines),
|
|
||||||
max(contained_lines))))
|
|
||||||
return (nodes_lines, first_line)
|
|
||||||
|
|
||||||
def _extract_directives(self, lines):
|
|
||||||
|
|
||||||
def starting_whitespace(line):
|
|
||||||
m = re.match(r"^(\s+)(.*)$", line)
|
|
||||||
if not m:
|
|
||||||
return 0
|
|
||||||
return len(m.group(1))
|
|
||||||
|
|
||||||
def all_whitespace(line):
|
|
||||||
return bool(re.match(r"^(\s*)$", line))
|
|
||||||
|
|
||||||
def find_directive_end(start, lines):
|
|
||||||
after_lines = collections.deque(lines[start + 1:])
|
|
||||||
k = 0
|
|
||||||
while after_lines:
|
|
||||||
line = after_lines.popleft()
|
|
||||||
if all_whitespace(line) or starting_whitespace(line) >= 1:
|
|
||||||
k += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return start + k
|
|
||||||
|
|
||||||
# Find where directives start & end so that we can exclude content in
|
|
||||||
# these directive regions (the rst parser may not handle this correctly
|
|
||||||
# for unknown directives, so we have to do it manually).
|
|
||||||
directives = []
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
if re.match(r"^\s*..\s(.*?)::\s*", line):
|
|
||||||
directives.append((i, find_directive_end(i, lines)))
|
|
||||||
elif re.match(r"^::\s*$", line):
|
|
||||||
directives.append((i, find_directive_end(i, lines)))
|
|
||||||
|
|
||||||
# Find definition terms in definition lists
|
|
||||||
# This check may match the code, which is already appended
|
|
||||||
lwhitespaces = r"^\s*"
|
|
||||||
listspattern = r"^\s*(\* |- |#\. |\d+\. )"
|
|
||||||
for i in range(0, len(lines) - 1):
|
|
||||||
line = lines[i]
|
|
||||||
next_line = lines[i + 1]
|
|
||||||
# if line is a blank, line is not a definition term
|
|
||||||
if all_whitespace(line):
|
|
||||||
continue
|
|
||||||
# if line is a list, line is checked as normal line
|
|
||||||
if re.match(listspattern, line):
|
|
||||||
continue
|
|
||||||
if (len(re.search(lwhitespaces, line).group()) <
|
|
||||||
len(re.search(lwhitespaces, next_line).group())):
|
|
||||||
directives.append((i, i))
|
|
||||||
|
|
||||||
return directives
|
|
||||||
|
|
||||||
def _txt_checker(self, parsed_file):
|
|
||||||
for i, line in enumerate(parsed_file.lines_iter()):
|
|
||||||
if len(line) > self._max_line_length:
|
|
||||||
if not utils.contains_url(line):
|
|
||||||
yield (i + 1, 'D001', 'Line too long')
|
|
||||||
|
|
||||||
def _rst_checker(self, parsed_file):
|
|
||||||
lines = list(parsed_file.lines_iter())
|
|
||||||
doc = parsed_file.document
|
|
||||||
nodes_lines, first_line = self._extract_node_lines(doc)
|
|
||||||
directives = self._extract_directives(lines)
|
|
||||||
|
|
||||||
def find_containing_nodes(num):
|
|
||||||
if num < first_line and len(nodes_lines):
|
|
||||||
return [nodes_lines[0][0]]
|
|
||||||
contained_in = []
|
|
||||||
for (n, (line_min, line_max)) in nodes_lines:
|
|
||||||
if num >= line_min and num <= line_max:
|
|
||||||
contained_in.append((n, (line_min, line_max)))
|
|
||||||
smallest_span = None
|
|
||||||
best_nodes = []
|
|
||||||
for (n, (line_min, line_max)) in contained_in:
|
|
||||||
span = line_max - line_min
|
|
||||||
if smallest_span is None:
|
|
||||||
smallest_span = span
|
|
||||||
best_nodes = [n]
|
|
||||||
elif span < smallest_span:
|
|
||||||
smallest_span = span
|
|
||||||
best_nodes = [n]
|
|
||||||
elif span == smallest_span:
|
|
||||||
best_nodes.append(n)
|
|
||||||
return best_nodes
|
|
||||||
|
|
||||||
def any_types(nodes, types):
|
|
||||||
return any([isinstance(n, types) for n in nodes])
|
|
||||||
|
|
||||||
skip_types = (
|
|
||||||
docutils_nodes.target,
|
|
||||||
docutils_nodes.literal_block,
|
|
||||||
)
|
|
||||||
title_types = (
|
|
||||||
docutils_nodes.title,
|
|
||||||
docutils_nodes.subtitle,
|
|
||||||
docutils_nodes.section,
|
|
||||||
)
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
if len(line) > self._max_line_length:
|
|
||||||
in_directive = False
|
|
||||||
for (start, end) in directives:
|
|
||||||
if i >= start and i <= end:
|
|
||||||
in_directive = True
|
|
||||||
break
|
|
||||||
if in_directive:
|
|
||||||
continue
|
|
||||||
stripped = line.lstrip()
|
|
||||||
if ' ' not in stripped:
|
|
||||||
# No room to split even if we could.
|
|
||||||
continue
|
|
||||||
if utils.contains_url(stripped):
|
|
||||||
continue
|
|
||||||
nodes = find_containing_nodes(i + 1)
|
|
||||||
if any_types(nodes, skip_types):
|
|
||||||
continue
|
|
||||||
if self._allow_long_titles and any_types(nodes, title_types):
|
|
||||||
continue
|
|
||||||
yield (i + 1, 'D001', 'Line too long')
|
|
||||||
|
|
||||||
def report_iter(self, parsed_file):
|
|
||||||
if parsed_file.extension.lower() != '.rst':
|
|
||||||
checker_func = self._txt_checker
|
|
||||||
else:
|
|
||||||
checker_func = self._rst_checker
|
|
||||||
for issue in checker_func(parsed_file):
|
|
||||||
yield issue
|
|
382
doc8/main.py
382
doc8/main.py
@ -1,382 +0,0 @@
|
|||||||
# Copyright (C) 2014 Ivan Melnikov <iv at altlinux dot org>
|
|
||||||
#
|
|
||||||
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
|
|
||||||
#
|
|
||||||
# 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:
|
|
||||||
- invalid rst format - D000
|
|
||||||
- lines should not be longer than 79 characters - D001
|
|
||||||
- RST exception: line with no whitespace except in the beginning
|
|
||||||
- RST exception: lines with http or https urls
|
|
||||||
- RST exception: literal blocks
|
|
||||||
- RST exception: rst target directives
|
|
||||||
- no trailing whitespace - D002
|
|
||||||
- no tabulation for indentation - D003
|
|
||||||
- no carriage returns (use unix newlines) - D004
|
|
||||||
- no newline at end of file - D005
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import collections
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Only useful for when running directly (for dev/debugging).
|
|
||||||
sys.path.insert(0, os.path.abspath(os.getcwd()))
|
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, os.getcwd())))
|
|
||||||
|
|
||||||
import six
|
|
||||||
from six.moves import configparser
|
|
||||||
from stevedore import extension
|
|
||||||
|
|
||||||
from doc8 import checks
|
|
||||||
from doc8 import parser as file_parser
|
|
||||||
from doc8 import utils
|
|
||||||
from doc8 import version
|
|
||||||
|
|
||||||
FILE_PATTERNS = ['.rst', '.txt']
|
|
||||||
MAX_LINE_LENGTH = 79
|
|
||||||
CONFIG_FILENAMES = [
|
|
||||||
"doc8.ini",
|
|
||||||
"tox.ini",
|
|
||||||
"pep8.ini",
|
|
||||||
"setup.cfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def split_set_type(text, delimiter=","):
|
|
||||||
return set([i.strip() for i in text.split(delimiter) if i.strip()])
|
|
||||||
|
|
||||||
|
|
||||||
def merge_sets(sets):
|
|
||||||
m = set()
|
|
||||||
for s in sets:
|
|
||||||
m.update(s)
|
|
||||||
return m
|
|
||||||
|
|
||||||
|
|
||||||
def parse_ignore_path_errors(entries):
|
|
||||||
ignore_path_errors = collections.defaultdict(set)
|
|
||||||
for path in entries:
|
|
||||||
path, ignored_errors = path.split(";", 1)
|
|
||||||
path = path.strip()
|
|
||||||
ignored_errors = split_set_type(ignored_errors, delimiter=";")
|
|
||||||
ignore_path_errors[path].update(ignored_errors)
|
|
||||||
return dict(ignore_path_errors)
|
|
||||||
|
|
||||||
|
|
||||||
def extract_config(args):
|
|
||||||
parser = configparser.RawConfigParser()
|
|
||||||
read_files = []
|
|
||||||
if args['config']:
|
|
||||||
for fn in args['config']:
|
|
||||||
with open(fn, 'r') as fh:
|
|
||||||
parser.readfp(fh, filename=fn)
|
|
||||||
read_files.append(fn)
|
|
||||||
else:
|
|
||||||
read_files.extend(parser.read(CONFIG_FILENAMES))
|
|
||||||
if not read_files:
|
|
||||||
return {}
|
|
||||||
cfg = {}
|
|
||||||
try:
|
|
||||||
cfg['max_line_length'] = parser.getint("doc8", "max-line-length")
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
cfg['ignore'] = split_set_type(parser.get("doc8", "ignore"))
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
cfg['ignore_path'] = split_set_type(parser.get("doc8",
|
|
||||||
"ignore-path"))
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
ignore_path_errors = parser.get("doc8", "ignore-path-errors")
|
|
||||||
ignore_path_errors = split_set_type(ignore_path_errors)
|
|
||||||
ignore_path_errors = parse_ignore_path_errors(ignore_path_errors)
|
|
||||||
cfg['ignore_path_errors'] = ignore_path_errors
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
cfg['allow_long_titles'] = parser.getboolean("doc8",
|
|
||||||
"allow-long-titles")
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
cfg['sphinx'] = parser.getboolean("doc8", "sphinx")
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
cfg['verbose'] = parser.getboolean("doc8", "verbose")
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
cfg['file_encoding'] = parser.get("doc8", "file-encoding")
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
cfg['default_extension'] = parser.get("doc8", "default-extension")
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
extensions = parser.get("doc8", "extensions")
|
|
||||||
extensions = extensions.split(",")
|
|
||||||
extensions = [s.strip() for s in extensions if s.strip()]
|
|
||||||
if extensions:
|
|
||||||
cfg['extension'] = extensions
|
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
|
||||||
pass
|
|
||||||
return cfg
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_checks(cfg):
|
|
||||||
base = [
|
|
||||||
checks.CheckValidity(cfg),
|
|
||||||
checks.CheckTrailingWhitespace(cfg),
|
|
||||||
checks.CheckIndentationNoTab(cfg),
|
|
||||||
checks.CheckCarriageReturn(cfg),
|
|
||||||
checks.CheckMaxLineLength(cfg),
|
|
||||||
checks.CheckNewlineEndOfFile(cfg),
|
|
||||||
]
|
|
||||||
mgr = extension.ExtensionManager(
|
|
||||||
namespace='doc8.extension.check',
|
|
||||||
invoke_on_load=True,
|
|
||||||
invoke_args=(cfg.copy(),),
|
|
||||||
)
|
|
||||||
addons = []
|
|
||||||
for e in mgr:
|
|
||||||
addons.append(e.obj)
|
|
||||||
return base + addons
|
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(verbose):
|
|
||||||
if verbose:
|
|
||||||
level = logging.DEBUG
|
|
||||||
else:
|
|
||||||
level = logging.ERROR
|
|
||||||
logging.basicConfig(level=level,
|
|
||||||
format='%(levelname)s: %(message)s', stream=sys.stdout)
|
|
||||||
|
|
||||||
|
|
||||||
def scan(cfg):
|
|
||||||
if not cfg.get('quiet'):
|
|
||||||
print("Scanning...")
|
|
||||||
files = collections.deque()
|
|
||||||
ignored_paths = cfg.get('ignore_path', [])
|
|
||||||
files_ignored = 0
|
|
||||||
file_iter = utils.find_files(cfg.get('paths', []),
|
|
||||||
cfg.get('extension', []), ignored_paths)
|
|
||||||
default_extension = cfg.get('default_extension')
|
|
||||||
file_encoding = cfg.get('file_encoding')
|
|
||||||
for filename, ignoreable in file_iter:
|
|
||||||
if ignoreable:
|
|
||||||
files_ignored += 1
|
|
||||||
if cfg.get('verbose'):
|
|
||||||
print(" Ignoring '%s'" % (filename))
|
|
||||||
else:
|
|
||||||
f = file_parser.parse(filename,
|
|
||||||
default_extension=default_extension,
|
|
||||||
encoding=file_encoding)
|
|
||||||
files.append(f)
|
|
||||||
if cfg.get('verbose'):
|
|
||||||
print(" Selecting '%s'" % (filename))
|
|
||||||
return (files, files_ignored)
|
|
||||||
|
|
||||||
|
|
||||||
def validate(cfg, files):
|
|
||||||
if not cfg.get('quiet'):
|
|
||||||
print("Validating...")
|
|
||||||
error_counts = {}
|
|
||||||
ignoreables = frozenset(cfg.get('ignore', []))
|
|
||||||
ignore_targeted = cfg.get('ignore_path_errors', {})
|
|
||||||
while files:
|
|
||||||
f = files.popleft()
|
|
||||||
if cfg.get('verbose'):
|
|
||||||
print("Validating %s" % f)
|
|
||||||
targeted_ignoreables = set(ignore_targeted.get(f.filename, set()))
|
|
||||||
targeted_ignoreables.update(ignoreables)
|
|
||||||
for c in fetch_checks(cfg):
|
|
||||||
try:
|
|
||||||
# http://legacy.python.org/dev/peps/pep-3155/
|
|
||||||
check_name = c.__class__.__qualname__
|
|
||||||
except AttributeError:
|
|
||||||
check_name = ".".join([c.__class__.__module__,
|
|
||||||
c.__class__.__name__])
|
|
||||||
error_counts.setdefault(check_name, 0)
|
|
||||||
try:
|
|
||||||
extension_matcher = c.EXT_MATCHER
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if not extension_matcher.match(f.extension):
|
|
||||||
if cfg.get('verbose'):
|
|
||||||
print(" Skipping check '%s' since it does not"
|
|
||||||
" understand parsing a file with extension '%s'"
|
|
||||||
% (check_name, f.extension))
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
reports = set(c.REPORTS)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
reports = reports - targeted_ignoreables
|
|
||||||
if not reports:
|
|
||||||
if cfg.get('verbose'):
|
|
||||||
print(" Skipping check '%s', determined to only"
|
|
||||||
" check ignoreable codes" % check_name)
|
|
||||||
continue
|
|
||||||
if cfg.get('verbose'):
|
|
||||||
print(" Running check '%s'" % check_name)
|
|
||||||
if isinstance(c, checks.ContentCheck):
|
|
||||||
for line_num, code, message in c.report_iter(f):
|
|
||||||
if code in targeted_ignoreables:
|
|
||||||
continue
|
|
||||||
if not isinstance(line_num, (float, int)):
|
|
||||||
line_num = "?"
|
|
||||||
if cfg.get('verbose'):
|
|
||||||
print(' - %s:%s: %s %s'
|
|
||||||
% (f.filename, line_num, code, message))
|
|
||||||
else:
|
|
||||||
print('%s:%s: %s %s'
|
|
||||||
% (f.filename, line_num, code, message))
|
|
||||||
error_counts[check_name] += 1
|
|
||||||
elif isinstance(c, checks.LineCheck):
|
|
||||||
for line_num, line in enumerate(f.lines_iter(), 1):
|
|
||||||
for code, message in c.report_iter(line):
|
|
||||||
if code in targeted_ignoreables:
|
|
||||||
continue
|
|
||||||
if cfg.get('verbose'):
|
|
||||||
print(' - %s:%s: %s %s'
|
|
||||||
% (f.filename, line_num, code, message))
|
|
||||||
else:
|
|
||||||
print('%s:%s: %s %s'
|
|
||||||
% (f.filename, line_num, code, message))
|
|
||||||
error_counts[check_name] += 1
|
|
||||||
else:
|
|
||||||
raise TypeError("Unknown check type: %s, %s"
|
|
||||||
% (type(c), c))
|
|
||||||
return error_counts
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
prog='doc8',
|
|
||||||
description=__doc__,
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
||||||
default_configs = ", ".join(CONFIG_FILENAMES)
|
|
||||||
parser.add_argument("paths", metavar='path', type=str, nargs='*',
|
|
||||||
help=("path to scan for doc files"
|
|
||||||
" (default: current directory)."),
|
|
||||||
default=[os.getcwd()])
|
|
||||||
parser.add_argument("--config", metavar='path', action="append",
|
|
||||||
help="user config file location"
|
|
||||||
" (default: %s)." % default_configs,
|
|
||||||
default=[])
|
|
||||||
parser.add_argument("--allow-long-titles", action="store_true",
|
|
||||||
help="allow long section titles (default: false).",
|
|
||||||
default=False)
|
|
||||||
parser.add_argument("--ignore", action="append", metavar="code",
|
|
||||||
help="ignore the given error code(s).",
|
|
||||||
type=split_set_type,
|
|
||||||
default=[])
|
|
||||||
parser.add_argument("--no-sphinx", action="store_false",
|
|
||||||
help="do not ignore sphinx specific false positives.",
|
|
||||||
default=True, dest='sphinx')
|
|
||||||
parser.add_argument("--ignore-path", action="append", default=[],
|
|
||||||
help="ignore the given directory or file (globs"
|
|
||||||
" are supported).", metavar='path')
|
|
||||||
parser.add_argument("--ignore-path-errors", action="append", default=[],
|
|
||||||
help="ignore the given specific errors in the"
|
|
||||||
" provided file.", metavar='path')
|
|
||||||
parser.add_argument("--default-extension", action="store",
|
|
||||||
help="default file extension to use when a file is"
|
|
||||||
" found without a file extension.",
|
|
||||||
default='', dest='default_extension',
|
|
||||||
metavar='extension')
|
|
||||||
parser.add_argument("--file-encoding", action="store",
|
|
||||||
help="override encoding to use when attempting"
|
|
||||||
" to determine an input files text encoding "
|
|
||||||
"(providing this avoids using `chardet` to"
|
|
||||||
" automatically detect encoding/s)",
|
|
||||||
default='', dest='file_encoding',
|
|
||||||
metavar='encoding')
|
|
||||||
parser.add_argument("--max-line-length", action="store", metavar="int",
|
|
||||||
type=int,
|
|
||||||
help="maximum allowed line"
|
|
||||||
" length (default: %s)." % MAX_LINE_LENGTH,
|
|
||||||
default=MAX_LINE_LENGTH)
|
|
||||||
parser.add_argument("-e", "--extension", action="append",
|
|
||||||
metavar="extension",
|
|
||||||
help="check file extensions of the given type"
|
|
||||||
" (default: %s)." % ", ".join(FILE_PATTERNS),
|
|
||||||
default=list(FILE_PATTERNS))
|
|
||||||
parser.add_argument("-q", "--quiet", action='store_true',
|
|
||||||
help="only print violations", default=False)
|
|
||||||
parser.add_argument("-v", "--verbose", dest="verbose", action='store_true',
|
|
||||||
help="run in verbose mode.", default=False)
|
|
||||||
parser.add_argument("--version", dest="version", action='store_true',
|
|
||||||
help="show the version and exit.", default=False)
|
|
||||||
args = vars(parser.parse_args())
|
|
||||||
if args.get('version'):
|
|
||||||
print(version.version_string())
|
|
||||||
return 0
|
|
||||||
args['ignore'] = merge_sets(args['ignore'])
|
|
||||||
cfg = extract_config(args)
|
|
||||||
args['ignore'].update(cfg.pop("ignore", set()))
|
|
||||||
if 'sphinx' in cfg:
|
|
||||||
args['sphinx'] = cfg.pop("sphinx")
|
|
||||||
args['extension'].extend(cfg.pop('extension', []))
|
|
||||||
args['ignore_path'].extend(cfg.pop('ignore_path', []))
|
|
||||||
|
|
||||||
cfg.setdefault('ignore_path_errors', {})
|
|
||||||
tmp_ignores = parse_ignore_path_errors(args.pop('ignore_path_errors', []))
|
|
||||||
for path, ignores in six.iteritems(tmp_ignores):
|
|
||||||
if path in cfg['ignore_path_errors']:
|
|
||||||
cfg['ignore_path_errors'][path].update(ignores)
|
|
||||||
else:
|
|
||||||
cfg['ignore_path_errors'][path] = set(ignores)
|
|
||||||
|
|
||||||
args.update(cfg)
|
|
||||||
setup_logging(args.get('verbose'))
|
|
||||||
|
|
||||||
files, files_ignored = scan(args)
|
|
||||||
files_selected = len(files)
|
|
||||||
error_counts = validate(args, files)
|
|
||||||
total_errors = sum(six.itervalues(error_counts))
|
|
||||||
|
|
||||||
if not args.get('quiet'):
|
|
||||||
print("=" * 8)
|
|
||||||
print("Total files scanned = %s" % (files_selected))
|
|
||||||
print("Total files ignored = %s" % (files_ignored))
|
|
||||||
print("Total accumulated errors = %s" % (total_errors))
|
|
||||||
if error_counts:
|
|
||||||
print("Detailed error counts:")
|
|
||||||
for check_name in sorted(six.iterkeys(error_counts)):
|
|
||||||
check_errors = error_counts[check_name]
|
|
||||||
print(" - %s = %s" % (check_name, check_errors))
|
|
||||||
|
|
||||||
if total_errors:
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
144
doc8/parser.py
144
doc8/parser.py
@ -1,144 +0,0 @@
|
|||||||
# Copyright (C) 2014 Ivan Melnikov <iv at altlinux dot org>
|
|
||||||
#
|
|
||||||
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
|
|
||||||
#
|
|
||||||
# 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 errno
|
|
||||||
import os
|
|
||||||
import threading
|
|
||||||
|
|
||||||
import chardet
|
|
||||||
from docutils import frontend
|
|
||||||
from docutils import parsers as docutils_parser
|
|
||||||
from docutils import utils
|
|
||||||
import restructuredtext_lint as rl
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
class ParsedFile(object):
|
|
||||||
FALLBACK_ENCODING = 'utf-8'
|
|
||||||
|
|
||||||
def __init__(self, filename, encoding=None, default_extension=''):
|
|
||||||
self._filename = filename
|
|
||||||
self._content = None
|
|
||||||
self._raw_content = None
|
|
||||||
self._encoding = encoding
|
|
||||||
self._doc = None
|
|
||||||
self._errors = None
|
|
||||||
self._lines = None
|
|
||||||
self._has_read = False
|
|
||||||
self._extension = os.path.splitext(filename)[1]
|
|
||||||
self._read_lock = threading.Lock()
|
|
||||||
if not self._extension:
|
|
||||||
self._extension = default_extension
|
|
||||||
|
|
||||||
@property
|
|
||||||
def errors(self):
|
|
||||||
if self._errors is not None:
|
|
||||||
return self._errors
|
|
||||||
self._errors = rl.lint(self.contents, filepath=self.filename)
|
|
||||||
return self._errors
|
|
||||||
|
|
||||||
@property
|
|
||||||
def document(self):
|
|
||||||
if self._doc is None:
|
|
||||||
# Use the rst parsers document output to do as much of the
|
|
||||||
# validation as we can without resorting to custom logic (this
|
|
||||||
# parser is what sphinx and others use anyway so it's hopefully
|
|
||||||
# mature).
|
|
||||||
parser_cls = docutils_parser.get_parser_class("rst")
|
|
||||||
parser = parser_cls()
|
|
||||||
defaults = {
|
|
||||||
'halt_level': 5,
|
|
||||||
'report_level': 5,
|
|
||||||
'quiet': True,
|
|
||||||
'file_insertion_enabled': False,
|
|
||||||
'traceback': True,
|
|
||||||
# Development use only.
|
|
||||||
'dump_settings': False,
|
|
||||||
'dump_internals': False,
|
|
||||||
'dump_transforms': False,
|
|
||||||
}
|
|
||||||
opt = frontend.OptionParser(components=[parser], defaults=defaults)
|
|
||||||
doc = utils.new_document(source_path=self.filename,
|
|
||||||
settings=opt.get_default_values())
|
|
||||||
parser.parse(self.contents, doc)
|
|
||||||
self._doc = doc
|
|
||||||
return self._doc
|
|
||||||
|
|
||||||
def _read(self):
|
|
||||||
if self._has_read:
|
|
||||||
return
|
|
||||||
with self._read_lock:
|
|
||||||
if not self._has_read:
|
|
||||||
with open(self.filename, 'rb') as fh:
|
|
||||||
self._lines = list(fh)
|
|
||||||
fh.seek(0)
|
|
||||||
self._raw_content = fh.read()
|
|
||||||
self._has_read = True
|
|
||||||
|
|
||||||
def lines_iter(self, remove_trailing_newline=True):
|
|
||||||
self._read()
|
|
||||||
for line in self._lines:
|
|
||||||
line = six.text_type(line, encoding=self.encoding)
|
|
||||||
if remove_trailing_newline and line.endswith("\n"):
|
|
||||||
line = line[0:-1]
|
|
||||||
yield line
|
|
||||||
|
|
||||||
@property
|
|
||||||
def lines(self):
|
|
||||||
self._read()
|
|
||||||
return self._lines
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extension(self):
|
|
||||||
return self._extension
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filename(self):
|
|
||||||
return self._filename
|
|
||||||
|
|
||||||
@property
|
|
||||||
def encoding(self):
|
|
||||||
if not self._encoding:
|
|
||||||
encoding = chardet.detect(self.raw_contents)['encoding']
|
|
||||||
if not encoding:
|
|
||||||
encoding = self.FALLBACK_ENCODING
|
|
||||||
self._encoding = encoding
|
|
||||||
return self._encoding
|
|
||||||
|
|
||||||
@property
|
|
||||||
def raw_contents(self):
|
|
||||||
self._read()
|
|
||||||
return self._raw_content
|
|
||||||
|
|
||||||
@property
|
|
||||||
def contents(self):
|
|
||||||
if self._content is None:
|
|
||||||
self._content = six.text_type(self.raw_contents,
|
|
||||||
encoding=self.encoding)
|
|
||||||
return self._content
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s (%s, %s chars, %s lines)" % (
|
|
||||||
self.filename, self.encoding, len(self.contents),
|
|
||||||
len(list(self.lines_iter())))
|
|
||||||
|
|
||||||
|
|
||||||
def parse(filename, encoding=None, default_extension=''):
|
|
||||||
if not os.path.isfile(filename):
|
|
||||||
raise IOError(errno.ENOENT, 'File not found', filename)
|
|
||||||
return ParsedFile(filename,
|
|
||||||
encoding=encoding,
|
|
||||||
default_extension=default_extension)
|
|
@ -1,191 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# 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 tempfile
|
|
||||||
|
|
||||||
import testtools
|
|
||||||
|
|
||||||
from doc8 import checks
|
|
||||||
from doc8 import parser
|
|
||||||
|
|
||||||
|
|
||||||
class TestTrailingWhitespace(testtools.TestCase):
|
|
||||||
def test_trailing(self):
|
|
||||||
lines = ["a b ", "ab"]
|
|
||||||
check = checks.CheckTrailingWhitespace({})
|
|
||||||
errors = []
|
|
||||||
for line in lines:
|
|
||||||
errors.extend(check.report_iter(line))
|
|
||||||
self.assertEqual(1, len(errors))
|
|
||||||
(code, msg) = errors[0]
|
|
||||||
self.assertIn(code, check.REPORTS)
|
|
||||||
|
|
||||||
|
|
||||||
class TestTabIndentation(testtools.TestCase):
|
|
||||||
def test_tabs(self):
|
|
||||||
lines = [" b", "\tabc", "efg", "\t\tc"]
|
|
||||||
check = checks.CheckIndentationNoTab({})
|
|
||||||
errors = []
|
|
||||||
for line in lines:
|
|
||||||
errors.extend(check.report_iter(line))
|
|
||||||
self.assertEqual(2, len(errors))
|
|
||||||
(code, msg) = errors[0]
|
|
||||||
self.assertIn(code, check.REPORTS)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCarriageReturn(testtools.TestCase):
|
|
||||||
def test_cr(self):
|
|
||||||
lines = ["\tabc", "efg", "\r\n"]
|
|
||||||
check = checks.CheckCarriageReturn({})
|
|
||||||
errors = []
|
|
||||||
for line in lines:
|
|
||||||
errors.extend(check.report_iter(line))
|
|
||||||
self.assertEqual(1, len(errors))
|
|
||||||
(code, msg) = errors[0]
|
|
||||||
self.assertIn(code, check.REPORTS)
|
|
||||||
|
|
||||||
|
|
||||||
class TestLineLength(testtools.TestCase):
|
|
||||||
def test_over_length(self):
|
|
||||||
content = b"""
|
|
||||||
===
|
|
||||||
aaa
|
|
||||||
===
|
|
||||||
|
|
||||||
----
|
|
||||||
test
|
|
||||||
----
|
|
||||||
|
|
||||||
"""
|
|
||||||
content += b"\n\n"
|
|
||||||
content += (b"a" * 60) + b" " + (b"b" * 60)
|
|
||||||
content += b"\n"
|
|
||||||
conf = {
|
|
||||||
'max_line_length': 79,
|
|
||||||
'allow_long_titles': True,
|
|
||||||
}
|
|
||||||
for ext in ['.rst', '.txt']:
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=ext) as fh:
|
|
||||||
fh.write(content)
|
|
||||||
fh.flush()
|
|
||||||
|
|
||||||
parsed_file = parser.ParsedFile(fh.name)
|
|
||||||
check = checks.CheckMaxLineLength(conf)
|
|
||||||
errors = list(check.report_iter(parsed_file))
|
|
||||||
self.assertEqual(1, len(errors))
|
|
||||||
(line, code, msg) = errors[0]
|
|
||||||
self.assertIn(code, check.REPORTS)
|
|
||||||
|
|
||||||
def test_correct_length(self):
|
|
||||||
conf = {
|
|
||||||
'max_line_length': 79,
|
|
||||||
'allow_long_titles': True,
|
|
||||||
}
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.rst') as fh:
|
|
||||||
fh.write(b'known exploit in the wild, for example'
|
|
||||||
b' \xe2\x80\x93 the time'
|
|
||||||
b' between advance notification')
|
|
||||||
fh.flush()
|
|
||||||
|
|
||||||
parsed_file = parser.ParsedFile(fh.name, encoding='utf-8')
|
|
||||||
check = checks.CheckMaxLineLength(conf)
|
|
||||||
errors = list(check.report_iter(parsed_file))
|
|
||||||
self.assertEqual(0, len(errors))
|
|
||||||
|
|
||||||
def test_ignore_code_block(self):
|
|
||||||
conf = {
|
|
||||||
'max_line_length': 79,
|
|
||||||
'allow_long_titles': True,
|
|
||||||
}
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.rst') as fh:
|
|
||||||
fh.write(b'List which contains items with code-block\n'
|
|
||||||
b'- this is a list item\n\n'
|
|
||||||
b' .. code-block:: ini\n\n'
|
|
||||||
b' this line exceeds 80 chars but should be ignored'
|
|
||||||
b'this line exceeds 80 chars but should be ignored'
|
|
||||||
b'this line exceeds 80 chars but should be ignored')
|
|
||||||
fh.flush()
|
|
||||||
|
|
||||||
parsed_file = parser.ParsedFile(fh.name, encoding='utf-8')
|
|
||||||
check = checks.CheckMaxLineLength(conf)
|
|
||||||
errors = list(check.report_iter(parsed_file))
|
|
||||||
self.assertEqual(0, len(errors))
|
|
||||||
|
|
||||||
def test_unsplittable_length(self):
|
|
||||||
content = b"""
|
|
||||||
===
|
|
||||||
aaa
|
|
||||||
===
|
|
||||||
|
|
||||||
----
|
|
||||||
test
|
|
||||||
----
|
|
||||||
|
|
||||||
"""
|
|
||||||
content += b"\n\n"
|
|
||||||
content += b"a" * 100
|
|
||||||
content += b"\n"
|
|
||||||
conf = {
|
|
||||||
'max_line_length': 79,
|
|
||||||
'allow_long_titles': True,
|
|
||||||
}
|
|
||||||
# This number is different since rst parsing is aware that titles
|
|
||||||
# are allowed to be over-length, while txt parsing is not aware of
|
|
||||||
# this fact (since it has no concept of title sections).
|
|
||||||
extensions = [(0, '.rst'), (1, '.txt')]
|
|
||||||
for expected_errors, ext in extensions:
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=ext) as fh:
|
|
||||||
fh.write(content)
|
|
||||||
fh.flush()
|
|
||||||
|
|
||||||
parsed_file = parser.ParsedFile(fh.name)
|
|
||||||
check = checks.CheckMaxLineLength(conf)
|
|
||||||
errors = list(check.report_iter(parsed_file))
|
|
||||||
self.assertEqual(expected_errors, len(errors))
|
|
||||||
|
|
||||||
def test_definition_term_length(self):
|
|
||||||
conf = {
|
|
||||||
'max_line_length': 79,
|
|
||||||
'allow_long_titles': True,
|
|
||||||
}
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.rst') as fh:
|
|
||||||
fh.write(b'Definition List which contains long term.\n\n'
|
|
||||||
b'looooooooooooooooooooooooooooooong definition term'
|
|
||||||
b'this line exceeds 80 chars but should be ignored\n'
|
|
||||||
b' this is a definition\n')
|
|
||||||
fh.flush()
|
|
||||||
|
|
||||||
parsed_file = parser.ParsedFile(fh.name, encoding='utf-8')
|
|
||||||
check = checks.CheckMaxLineLength(conf)
|
|
||||||
errors = list(check.report_iter(parsed_file))
|
|
||||||
self.assertEqual(0, len(errors))
|
|
||||||
|
|
||||||
|
|
||||||
class TestNewlineEndOfFile(testtools.TestCase):
|
|
||||||
def test_newline(self):
|
|
||||||
tests = [(1, b"testing"),
|
|
||||||
(1, b"testing\ntesting"),
|
|
||||||
(0, b"testing\n"),
|
|
||||||
(0, b"testing\ntesting\n")]
|
|
||||||
|
|
||||||
for expected_errors, line in tests:
|
|
||||||
with tempfile.NamedTemporaryFile() as fh:
|
|
||||||
fh.write(line)
|
|
||||||
fh.flush()
|
|
||||||
parsed_file = parser.ParsedFile(fh.name)
|
|
||||||
check = checks.CheckNewlineEndOfFile({})
|
|
||||||
errors = list(check.report_iter(parsed_file))
|
|
||||||
self.assertEqual(expected_errors, len(errors))
|
|
@ -1,77 +0,0 @@
|
|||||||
# Copyright (C) 2014 Ivan Melnikov <iv at altlinux dot org>
|
|
||||||
#
|
|
||||||
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
|
|
||||||
#
|
|
||||||
# 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 glob
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def find_files(paths, extensions, ignored_paths):
|
|
||||||
extensions = set(extensions)
|
|
||||||
ignored_absolute_paths = set()
|
|
||||||
for path in ignored_paths:
|
|
||||||
for expanded_path in glob.iglob(path):
|
|
||||||
expanded_path = os.path.abspath(expanded_path)
|
|
||||||
ignored_absolute_paths.add(expanded_path)
|
|
||||||
|
|
||||||
def extension_matches(path):
|
|
||||||
_base, ext = os.path.splitext(path)
|
|
||||||
return ext in extensions
|
|
||||||
|
|
||||||
def path_ignorable(path):
|
|
||||||
path = os.path.abspath(path)
|
|
||||||
if path in ignored_absolute_paths:
|
|
||||||
return True
|
|
||||||
last_path = None
|
|
||||||
while path != last_path:
|
|
||||||
# If we hit the root, this loop will stop since the resolution
|
|
||||||
# of "/../" is still "/" when ran through the abspath function...
|
|
||||||
last_path = path
|
|
||||||
path = os.path.abspath(os.path.join(path, os.path.pardir))
|
|
||||||
if path in ignored_absolute_paths:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
for path in paths:
|
|
||||||
if os.path.isfile(path):
|
|
||||||
if extension_matches(path):
|
|
||||||
yield (path, path_ignorable(path))
|
|
||||||
elif os.path.isdir(path):
|
|
||||||
for root, dirnames, filenames in os.walk(path):
|
|
||||||
for filename in filenames:
|
|
||||||
path = os.path.join(root, filename)
|
|
||||||
if extension_matches(path):
|
|
||||||
yield (path, path_ignorable(path))
|
|
||||||
else:
|
|
||||||
raise IOError('Invalid path: %s' % path)
|
|
||||||
|
|
||||||
|
|
||||||
def filtered_traverse(document, filter_func):
|
|
||||||
for n in document.traverse(include_self=True):
|
|
||||||
if filter_func(n):
|
|
||||||
yield n
|
|
||||||
|
|
||||||
|
|
||||||
def contains_url(line):
|
|
||||||
return "http://" in line or "https://" in line
|
|
||||||
|
|
||||||
|
|
||||||
def has_any_node_type(node, node_types):
|
|
||||||
n = node
|
|
||||||
while n is not None:
|
|
||||||
if isinstance(n, node_types):
|
|
||||||
return True
|
|
||||||
n = n.parent
|
|
||||||
return False
|
|
@ -1,24 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
try:
|
|
||||||
from pbr import version as pbr_version
|
|
||||||
_version_info = pbr_version.VersionInfo('doc8')
|
|
||||||
version_string = _version_info.version_string
|
|
||||||
except ImportError:
|
|
||||||
import pkg_resources
|
|
||||||
_version_info = pkg_resources.get_distribution('doc8')
|
|
||||||
version_string = lambda: _version_info.version
|
|
31
pylintrc
31
pylintrc
@ -1,31 +0,0 @@
|
|||||||
# The format of this file isn't really documented; just use --generate-rcfile
|
|
||||||
|
|
||||||
[Messages Control]
|
|
||||||
# C0111: Don't require docstrings on every method
|
|
||||||
# W0511: TODOs in code comments are fine.
|
|
||||||
# W0142: *args and **kwargs are fine.
|
|
||||||
# W0622: Redefining id is fine.
|
|
||||||
disable=C0111,W0511,W0142,W0622
|
|
||||||
|
|
||||||
[Basic]
|
|
||||||
# Variable names can be 1 to 31 characters long, with lowercase and underscores
|
|
||||||
variable-rgx=[a-z_][a-z0-9_]{0,30}$
|
|
||||||
|
|
||||||
# Argument names can be 2 to 31 characters long, with lowercase and underscores
|
|
||||||
argument-rgx=[a-z_][a-z0-9_]{1,30}$
|
|
||||||
|
|
||||||
# Method names should be at least 3 characters long
|
|
||||||
# and be lowercased with underscores
|
|
||||||
method-rgx=([a-z_][a-z0-9_]{2,50}|setUp|tearDown)$
|
|
||||||
|
|
||||||
[Design]
|
|
||||||
max-public-methods=100
|
|
||||||
min-public-methods=0
|
|
||||||
max-args=6
|
|
||||||
|
|
||||||
[Variables]
|
|
||||||
|
|
||||||
# List of additional names supposed to be defined in builtins. Remember that
|
|
||||||
# you should avoid to define new builtins when possible.
|
|
||||||
# _ is used by our localization
|
|
||||||
additional-builtins=_
|
|
@ -1,9 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
|
|
||||||
chardet
|
|
||||||
docutils
|
|
||||||
restructuredtext-lint>=0.7
|
|
||||||
six
|
|
||||||
stevedore
|
|
33
setup.cfg
33
setup.cfg
@ -1,33 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = doc8
|
|
||||||
summary = Style checker for Sphinx (or other) RST documentation
|
|
||||||
description-file =
|
|
||||||
README.rst
|
|
||||||
author = OpenStack
|
|
||||||
author-email = openstack-dev@lists.openstack.org
|
|
||||||
home-page = https://launchpad.net/doc8
|
|
||||||
classifier =
|
|
||||||
Intended Audience :: Information Technology
|
|
||||||
Intended Audience :: System Administrators
|
|
||||||
Intended Audience :: Developers
|
|
||||||
Development Status :: 4 - Beta
|
|
||||||
Topic :: Utilities
|
|
||||||
License :: OSI Approved :: Apache Software License
|
|
||||||
Operating System :: POSIX :: Linux
|
|
||||||
Programming Language :: Python
|
|
||||||
Programming Language :: Python :: 2
|
|
||||||
Programming Language :: Python :: 2.7
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3.4
|
|
||||||
|
|
||||||
[entry_points]
|
|
||||||
console_scripts =
|
|
||||||
doc8 = doc8.main:main
|
|
||||||
|
|
||||||
[build_sphinx]
|
|
||||||
all_files = 1
|
|
||||||
build-dir = doc/build
|
|
||||||
source-dir = doc/source
|
|
||||||
|
|
||||||
[wheel]
|
|
||||||
universal = 1
|
|
30
setup.py
30
setup.py
@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
|
||||||
import setuptools
|
|
||||||
|
|
||||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
|
||||||
# setuptools if some other modules registered functions in `atexit`.
|
|
||||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
|
||||||
try:
|
|
||||||
import multiprocessing # noqa
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
setuptools.setup(
|
|
||||||
setup_requires=['pbr'],
|
|
||||||
pbr=True)
|
|
@ -1,10 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
|
|
||||||
doc8
|
|
||||||
hacking>=0.9.2,<0.10
|
|
||||||
nose
|
|
||||||
oslosphinx
|
|
||||||
sphinx>=1.1.2,!=1.2.0,<1.3
|
|
||||||
testtools
|
|
32
tox.ini
32
tox.ini
@ -1,32 +0,0 @@
|
|||||||
[tox]
|
|
||||||
minversion = 1.6
|
|
||||||
skipsdist = True
|
|
||||||
envlist = py35,py27,pep8
|
|
||||||
|
|
||||||
[testenv]
|
|
||||||
setenv = VIRTUAL_ENV={envdir}
|
|
||||||
usedevelop = True
|
|
||||||
install_command = pip install {opts} {packages}
|
|
||||||
deps = -r{toxinidir}/requirements.txt
|
|
||||||
-r{toxinidir}/test-requirements.txt
|
|
||||||
commands = nosetests {posargs}
|
|
||||||
|
|
||||||
[testenv:pep8]
|
|
||||||
commands = flake8 {posargs}
|
|
||||||
|
|
||||||
[testenv:pylint]
|
|
||||||
requirements = pylint==0.25.2
|
|
||||||
commands = pylint doc8
|
|
||||||
|
|
||||||
[testenv:venv]
|
|
||||||
commands = {posargs}
|
|
||||||
|
|
||||||
[testenv:docs]
|
|
||||||
commands =
|
|
||||||
doc8 -e .rst doc CONTRIBUTING.rst HACKING.rst README.rst
|
|
||||||
python setup.py build_sphinx
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
builtins = _
|
|
||||||
show-source = True
|
|
||||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
|
|
Loading…
Reference in New Issue
Block a user