From 6e0373f9f250e822f1fc563a2f6fc06a495866cf Mon Sep 17 00:00:00 2001 From: Nhomar Hernandez Date: Thu, 26 Oct 2017 05:34:49 -0500 Subject: [PATCH] [IMP] mqt: First step to opensource in the right way is have a proper place where document, then, this change does that, only the minimal structure to have all the documentation auto-generated and auto-tested MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [IMP] Adding files to work as a complete documented package. - Added minimal cli structure (didactical commit). - activating tox [FIX] adding run-test to standarize the test environment. Add missing file to manifest [IMP] command `mqt lint` command is working as a shell command. [IMP] Remove all reference to name TRAVIS in filenames and variables. - All what will be related to travis specific is the one that should be called **travis** if not then use agnostic-brand names. m [IMP] mqt lint: Taking a defailt cfg in order to save lint config file declaration when not needed. [IMP] mqt flake8 running (yet things to do) [REF] s/travis_helpers/helpers/g no reference to helpers. m [IMP] more cleanup doc is compiling properlly with the new approach. [IMP] Several fixes in the doc and naming yet wip [DEL] cleaned file already moved to lints.py bumpversion Bump version: 0.1.0 → 0.1.1 WIP: Converting Pylint process in a class m tmp --- .bumpversion.cfg | 7 + AUTHORS.rst | 13 ++ CHANGES | 25 +++ CONTRIBUTING.rst | 111 +++++++++++++ MANIFEST.in | 9 ++ Makefile | 54 +++++++ README.md => README.rst | 32 ++-- SCRIPTS | 2 + TODO | 16 ++ docs/Makefile | 177 +++++++++++++++++++++ docs/api.rst | 11 ++ docs/authors.rst | 1 + docs/conf.py | 286 ++++++++++++++++++++++++++++++++++ docs/contributing.rst | 1 + docs/history.rst | 8 + docs/index.rst | 30 ++++ docs/installation.rst | 12 ++ docs/make.bat | 242 ++++++++++++++++++++++++++++ docs/modules.rst | 7 + docs/mqt.rst | 69 ++++++++ docs/mqt.testsuite.rst | 30 ++++ docs/requirements.txt | 3 + docs/roadmap.rst | 8 + docs/usage.rst | 7 + mqt/__about__.py | 8 + mqt/__init__.py | 15 ++ mqt/_compat.py | 83 ++++++++++ mqt/cfg/flake8.cfg | 7 + mqt/cfg/flake8__init__.cfg | 9 ++ mqt/cfg/pylint.cfg | 57 +++++++ mqt/cfg/pylint_beta.cfg | 75 +++++++++ mqt/cfg/pylint_exclude_61.cfg | 27 ++++ mqt/cfg/pylint_exclude_70.cfg | 27 ++++ mqt/cfg/pylint_pr.cfg | 123 +++++++++++++++ mqt/cli.py | 48 ++++++ mqt/getaddons.py | 133 ++++++++++++++++ mqt/git_run.py | 52 +++++++ mqt/helpers.py | 36 +++++ mqt/lints.py | 279 +++++++++++++++++++++++++++++++++ mqt/mqt.py | 156 +++++++++++++++++++ mqt/testsuite/__init__.py | 219 ++++++++++++++++++++++++++ mqt/testsuite/helpers.py | 66 ++++++++ mqt/testsuite/mqt.py | 37 +++++ requirements.txt | 3 + run-tests.py | 5 + sample_files/.gitignore | 1 + setup.py | 76 +++++++++ tox.ini | 9 ++ travis/test_pylint | 2 - 49 files changed, 2698 insertions(+), 16 deletions(-) create mode 100644 .bumpversion.cfg create mode 100644 AUTHORS.rst create mode 100644 CHANGES create mode 100644 CONTRIBUTING.rst create mode 100644 MANIFEST.in create mode 100644 Makefile rename README.md => README.rst (89%) create mode 100644 SCRIPTS create mode 100644 TODO create mode 100644 docs/Makefile create mode 100644 docs/api.rst create mode 100644 docs/authors.rst create mode 100755 docs/conf.py create mode 100644 docs/contributing.rst create mode 100644 docs/history.rst create mode 100644 docs/index.rst create mode 100644 docs/installation.rst create mode 100644 docs/make.bat create mode 100644 docs/modules.rst create mode 100644 docs/mqt.rst create mode 100644 docs/mqt.testsuite.rst create mode 100644 docs/requirements.txt create mode 100644 docs/roadmap.rst create mode 100644 docs/usage.rst create mode 100644 mqt/__about__.py create mode 100755 mqt/__init__.py create mode 100644 mqt/_compat.py create mode 100644 mqt/cfg/flake8.cfg create mode 100644 mqt/cfg/flake8__init__.cfg create mode 100644 mqt/cfg/pylint.cfg create mode 100644 mqt/cfg/pylint_beta.cfg create mode 100644 mqt/cfg/pylint_exclude_61.cfg create mode 100644 mqt/cfg/pylint_exclude_70.cfg create mode 100644 mqt/cfg/pylint_pr.cfg create mode 100644 mqt/cli.py create mode 100755 mqt/getaddons.py create mode 100644 mqt/git_run.py create mode 100644 mqt/helpers.py create mode 100755 mqt/lints.py create mode 100755 mqt/mqt.py create mode 100755 mqt/testsuite/__init__.py create mode 100644 mqt/testsuite/helpers.py create mode 100755 mqt/testsuite/mqt.py create mode 100755 run-tests.py create mode 100755 setup.py create mode 100644 tox.ini diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 000000000..d82753c8a --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,7 @@ +[bumpversion] +current_version = 0.1.1 +commit = True +tag = True + +[bumpversion:file:mqt/__about__.py] + diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 000000000..6e7ec1fc8 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,13 @@ +======= +Credits +======= + +Development Lead +---------------- + +* Odoo Community Association + +Contributors +------------ + +None yet. Why not be the first? diff --git a/CHANGES b/CHANGES new file mode 100644 index 000000000..38865cfce --- /dev/null +++ b/CHANGES @@ -0,0 +1,25 @@ +========= +Changelog +========= + +Here you can find the recent changes to mqt.. + +.. changelog:: + :version: dev + :released: Ongoing + + .. change:: + :tags: docs + + Updated CHANGES. + +.. changelog:: + :version: 0.1.0 + :released: 2017-10-26 + + .. change:: + :tags: project + + First release on PyPi. + +.. todo:: vim: set filetype=rst: diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 000000000..9f73c60f2 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,111 @@ +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/oca/mqt/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" +is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "feature" +is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +mqt could always use more documentation, whether as part of the +official mqt docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/oca/mqt/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +Get Started! +------------ + +Ready to contribute? Here's how to set up `mqt` for local development. + +1. Fork the `mqt` repo on GitHub. +2. Clone your fork locally:: + + $ git clone git@github.com:your_name_here/mqt.git + +3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: + + $ mkvirtualenv mqt + $ cd mqt/ + $ python setup.py develop + +4. Create a branch for local development:: + + $ git checkout -b name-of-your-bugfix-or-feature + + Now you can make your changes locally. + +5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: + + $ flake8 mqt tests + $ python setup.py test + $ tox + + To get flake8 and tox, just pip install them into your virtualenv. + +6. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +7. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 2.6, 2.7, and 3.3, and for PyPy. Check + https://travis-ci.org/oca/mqt/pull_requests + and make sure that the tests pass for all supported Python versions. + +Tips +---- + +To run a subset of tests:: + + $ python -m unittest tests.test_mqt diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..2e6f4403a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +include AUTHORS.rst +include CONTRIBUTING.rst +include CHANGES +include TODO +include LICENSE +include README.rst +include package_metadata.py +include requirements.txt +include SCRIPTS diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..0ca761eaf --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +.PHONY: clean-pyc clean-build docs + +help: + @echo "clean-build - remove build artifacts" + @echo "clean-pyc - remove Python file artifacts" + @echo "lint - check style with flake8" + @echo "test - run tests quickly with the default Python" + @echo "testall - run tests on every Python version with tox" + @echo "coverage - check code coverage quickly with the default Python" + @echo "docs - generate Sphinx HTML documentation, including API docs" + @echo "release - package and upload a release" + @echo "sdist - package" + +clean: clean-build clean-pyc + +clean-build: + rm -fr build/ + rm -fr dist/ + rm -fr *.egg-info + +clean-pyc: + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + +lint: + flake8 mqt tests + +test: + python setup.py test + +test-all: + tox + +coverage: + coverage run --source mqt setup.py test + coverage report -m + coverage html + open htmlcov/index.html + +docs: + rm -f docs/mqt.rst + rm -f docs/modules.rst + sphinx-apidoc -o docs/ mqt + $(MAKE) -C docs clean + $(MAKE) -C docs html + open docs/_build/html/index.html + +release: clean + python setup.py sdist upload + +sdist: clean + python setup.py sdist + ls -l dist diff --git a/README.md b/README.rst similarity index 89% rename from README.md rename to README.rst index eb3ce6f88..c6071eed6 100644 --- a/README.md +++ b/README.rst @@ -32,13 +32,15 @@ Multiple values for environment variable VERSION You can use branch or pull request into environment variable VERSION: - Branch 10.0 + ``` - VERSION="10.0" ODOO_REPO="odoo/odoo" +VERSION="10.0" ODOO_REPO="odoo/odoo" ``` - Pull request odoo/odoo#143 + ``` - VERSION="pull/143" ODOO_REPO="odoo/odoo" +VERSION="pull/143" ODOO_REPO="odoo/odoo" ``` Using custom branch inside odoo repository using ODOO_BRANCH @@ -48,8 +50,9 @@ You can use the custom branch into the ODOO_REPO using the environment variable - Branch saas-17 + ``` - ODOO_REPO="odoo/odoo" ODOO_BRANCH="saas-17" +ODOO_REPO="odoo/odoo" ODOO_BRANCH="saas-17" ``` Module unit tests @@ -61,8 +64,9 @@ Activate it through the `UNIT_TEST` directive. An additional line should be added to the `env:` section, similar to this one: - - VERSION="8.0" UNIT_TEST="1" - +``` +VERSION="8.0" UNIT_TEST="1" +``` Coveralls/Codecov configuration file ------------------------------------ @@ -86,15 +90,16 @@ templates in order to speed up your CI pipeline. Just specify at will: `MQT_TEMPLATE_DB='mqt_odoo_template' MQT_TEST_DB='mqt_odoo_test'`. Give us feedback on you experiences, and if you could share findings -from your use case, there might be some grateful people arround. - +from your use case, there might be some grateful people around. Isolated pylint+flake8 checks ----------------------------- If you want to make a build exclusive for these checks, you can add a line on the `env:` section of the .travis.yml file with this content: - - VERSION="7.0" LINT_CHECK="1" +``` +VERSION="7.0" LINT_CHECK="1" +``` You will get a faster answer about these questions and also a fast view over semaphore icons in Travis build view. @@ -102,13 +107,12 @@ semaphore icons in Travis build view. To avoid making again these checks on other builds, you have to add LINT_CHECK="0" variable on the line: - - VERSION="7.0" ODOO_REPO="odoo/odoo" LINT_CHECK="0" - +``` +VERSION="7.0" ODOO_REPO="odoo/odoo" LINT_CHECK="0" +``` Disable test ------------ If you want to make a build without tests, you can use the following directive: -`TEST_ENABLE="0"` - -You will simply get the databases with packages installed, -but whithout running any tests. +`TEST_ENABLE="0"` You will simply get the databases with packages installed, +but without running any tests. diff --git a/SCRIPTS b/SCRIPTS new file mode 100644 index 000000000..47bebd19f --- /dev/null +++ b/SCRIPTS @@ -0,0 +1,2 @@ +[console_scripts] +mqt=mqt.cli:cli diff --git a/TODO b/TODO new file mode 100644 index 000000000..065b2c9aa --- /dev/null +++ b/TODO @@ -0,0 +1,16 @@ +==== +TODO +==== + +dev +--- + +- Write the tests +- Write the project +- Release +- Optimize for clarity +- Release +- Optimize for speed +- Release + +.. todo:: vim: set filetype=rst: diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..0e35bee91 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/complexity.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/complexity.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/complexity" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/complexity" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 000000000..94123faea --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,11 @@ +.. _api: + +=== +API +=== + +.. automodule:: mqt + :members: + + .. autoclass:: Mqt + :members: diff --git a/docs/authors.rst b/docs/authors.rst new file mode 100644 index 000000000..e122f914a --- /dev/null +++ b/docs/authors.rst @@ -0,0 +1 @@ +.. include:: ../AUTHORS.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100755 index 000000000..d2f981017 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# complexity documentation build configuration file, created by +# sphinx-quickstart on Tue Jul 9 22:26:36 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# Get the project root dir, which is the parent dir of this +cwd = os.getcwd() +project_root = os.path.dirname(cwd) + +# Insert the project root dir as the first element in the PYTHONPATH. +# This lets us ensure that the source package is imported, and that its +# version is used. +sys.path.insert(0, project_root) + +# package data +about = {} +with open("../mqt/__about__.py") as fp: + exec(fp.read(), about) + + +import mqt + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# 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', + 'sphinx.ext.viewcode', + 'sphinx.ext.todo', + 'changelog' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = about['__title__'] +copyright = about['__copyright__'] + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '%s' % ('.'.join(about['__version__'].split('.'))[:2]) +# The full version, including alpha/beta/rc tags. +release = '%s' % (about['__version__']) + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# 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 + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = '%sdoc' % about['__title__'] + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', '{0}.tex'.format(about['__package_name__']), '{0} Documentation'.format(about['__title__']), + about['__author__'], 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', about['__package_name__'], '{0} Documentation'.format(about['__title__']), + about['__author__'], 1), +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', '{0}'.format(about['__package_name__']), '{0} Documentation'.format(about['__title__']), + about['__author__'], about['__package_name__'], about['__description__'], 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None, 'pip': ('http://sphinx.readthedocs.org/en/latest/', None)} + +# section names - optional +changelog_sections = ["general", "rendering", "tests", "docs"] + +# tags to sort on inside of sections - also optional +changelog_inner_tag_sort = ["feature", "bug"] + +# how to render changelog links - these are plain +# python string templates, ticket/pullreq/changeset number goes +# in "%s" +changelog_render_ticket = "https://github.com/oca/mqt/issues/%s" +changelog_render_pullreq = "https://github.com/oca/mqt/pulls/%s" +changelog_render_changeset = "https://github.com/oca/mqt/commit/%s" diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 000000000..e582053ea --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1 @@ +.. include:: ../CONTRIBUTING.rst diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 000000000..37c6fae3b --- /dev/null +++ b/docs/history.rst @@ -0,0 +1,8 @@ +.. _history: + +======= +History +======= + +.. include:: ../CHANGES + :start-line: 5 diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..8e878ccbf --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,30 @@ +.. complexity documentation master file, created by + sphinx-quickstart on Tue Jul 9 22:26:36 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to mqt's documentation! +=============================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + installation + usage + contributing + authors + roadmap + history + modules + +.. include:: ../README.rst + :start-line: 23 + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 000000000..607bc784a --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,12 @@ +============ +Installation +============ + +At the command line:: + + $ easy_install mqt + +Or, if you have virtualenvwrapper installed:: + + $ mkvirtualenv mqt + $ pip install mqt diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000..2df9a8cbb --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,242 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\complexity.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\complexity.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/modules.rst b/docs/modules.rst new file mode 100644 index 000000000..cf804b1e7 --- /dev/null +++ b/docs/modules.rst @@ -0,0 +1,7 @@ +mqt +=== + +.. toctree:: + :maxdepth: 4 + + mqt diff --git a/docs/mqt.rst b/docs/mqt.rst new file mode 100644 index 000000000..7c72617a8 --- /dev/null +++ b/docs/mqt.rst @@ -0,0 +1,69 @@ +mqt package +=========== + +Subpackages +----------- + +.. toctree:: + + mqt.testsuite + +Submodules +---------- + +mqt.cli module +-------------- + +.. automodule:: mqt.cli + :members: + :undoc-members: + :show-inheritance: + +mqt.getaddons module +-------------------- + +.. automodule:: mqt.getaddons + :members: + :undoc-members: + :show-inheritance: + +mqt.git_run module +------------------ + +.. automodule:: mqt.git_run + :members: + :undoc-members: + :show-inheritance: + +mqt.helpers module +------------------ + +.. automodule:: mqt.helpers + :members: + :undoc-members: + :show-inheritance: + +mqt.lints module +---------------- + +.. automodule:: mqt.lints + :members: + :undoc-members: + :show-inheritance: + +mqt.mqt module +-------------- + +.. automodule:: mqt.mqt + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: mqt + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/mqt.testsuite.rst b/docs/mqt.testsuite.rst new file mode 100644 index 000000000..a51468d9c --- /dev/null +++ b/docs/mqt.testsuite.rst @@ -0,0 +1,30 @@ +mqt.testsuite package +===================== + +Submodules +---------- + +mqt.testsuite.helpers module +---------------------------- + +.. automodule:: mqt.testsuite.helpers + :members: + :undoc-members: + :show-inheritance: + +mqt.testsuite.mqt module +------------------------ + +.. automodule:: mqt.testsuite.mqt + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: mqt.testsuite + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..5147ab0ca --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +-r ../requirements.txt +changelog +sphinx diff --git a/docs/roadmap.rst b/docs/roadmap.rst new file mode 100644 index 000000000..b565aaf34 --- /dev/null +++ b/docs/roadmap.rst @@ -0,0 +1,8 @@ +.. _roadmap: + +======= +Roadmap +======= + +.. include:: ../TODO + :start-line: 4 diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 000000000..aa87cd9ae --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,7 @@ +======== +Usage +======== + +To use mqt in a project:: + + import mqt diff --git a/mqt/__about__.py b/mqt/__about__.py new file mode 100644 index 000000000..12fc4cd30 --- /dev/null +++ b/mqt/__about__.py @@ -0,0 +1,8 @@ +__title__ = 'mqt' +__package_name__ = 'mqt' +__author__ = 'Odoo Community Association' +__description__ = 'QA Tools for Odoo maintainers (MQT)' +__email__ = 'github@odoo-community.org' +__version__ = '0.1.1' +__license__ = 'AGPL-v3' +__copyright__ = 'Copyright 2017 Odoo Community Association' diff --git a/mqt/__init__.py b/mqt/__init__.py new file mode 100755 index 000000000..612c010c6 --- /dev/null +++ b/mqt/__init__.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + +mqt +--- + +QA Tools for Odoo maintainers (MQT) + +""" + +from __future__ import absolute_import, division, print_function, \ + with_statement, unicode_literals + +from .mqt import Mqt diff --git a/mqt/_compat.py b/mqt/_compat.py new file mode 100644 index 000000000..5178299ec --- /dev/null +++ b/mqt/_compat.py @@ -0,0 +1,83 @@ +import sys + +PY2 = sys.version_info[0] == 2 + +_identity = lambda x: x + + +if PY2: + unichr = unichr + text_type = unicode + string_types = (str, unicode) + integer_types = (int, long) + from urllib import urlretrieve + + text_to_native = lambda s, enc: s.encode(enc) + + iterkeys = lambda d: d.iterkeys() + itervalues = lambda d: d.itervalues() + iteritems = lambda d: d.iteritems() + + from cStringIO import StringIO as BytesIO + from StringIO import StringIO + import cPickle as pickle + import ConfigParser as configparser + + from itertools import izip, imap + range_type = xrange + + cmp = cmp + + input = raw_input + from string import lower as ascii_lowercase + import urlparse + + def console_to_str(s): + return s.decode('utf_8') + + exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') + +else: + unichr = chr + text_type = str + string_types = (str,) + integer_types = (int, ) + + text_to_native = lambda s, enc: s + + iterkeys = lambda d: iter(d.keys()) + itervalues = lambda d: iter(d.values()) + iteritems = lambda d: iter(d.items()) + + from io import StringIO, BytesIO + import pickle + import configparser + + izip = zip + imap = map + range_type = range + + cmp = lambda a, b: (a > b) - (a < b) + + input = input + from string import ascii_lowercase + import urllib.parse as urllib + import urllib.parse as urlparse + from urllib.request import urlretrieve + + console_encoding = sys.__stdout__.encoding + + def console_to_str(s): + """ From pypa/pip project, pip.backwardwardcompat. License MIT. """ + try: + return s.decode(console_encoding) + except UnicodeDecodeError: + return s.decode('utf_8') + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise(value.with_traceback(tb)) + raise value + + +number_types = integer_types + (float,) diff --git a/mqt/cfg/flake8.cfg b/mqt/cfg/flake8.cfg new file mode 100644 index 000000000..0f8e8de3b --- /dev/null +++ b/mqt/cfg/flake8.cfg @@ -0,0 +1,7 @@ +[flake8] +# E123,E133,E226,E241,E242 are ignored by default by pep8 and flake8 +# F811 is legal in odoo 8 when we implement 2 interfaces for a method +# F999 pylint support this case with expected tests +ignore = E123,E133,E226,E241,E242,F811,F601 +max-line-length = 79 +exclude = __unported__,__init__.py diff --git a/mqt/cfg/flake8__init__.cfg b/mqt/cfg/flake8__init__.cfg new file mode 100644 index 000000000..31b25a2ea --- /dev/null +++ b/mqt/cfg/flake8__init__.cfg @@ -0,0 +1,9 @@ +[flake8] +# E123,E133,E226,E241,E242 are ignored by default by pep8 and flake8 +# F401 is legal in odoo __init__.py files +# F999 pylint support this case with expected tests +ignore = E123,E133,E226,E241,E242,F401,F601 +max-line-length = 79 +exclude = __unported__ +filename = __init__.py + diff --git a/mqt/cfg/pylint.cfg b/mqt/cfg/pylint.cfg new file mode 100644 index 000000000..f5614ad9d --- /dev/null +++ b/mqt/cfg/pylint.cfg @@ -0,0 +1,57 @@ +[MASTER] +profile=no +ignore=CVS,.git,scenarios,.bzr +persistent=yes +cache-size=500 + +[MESSAGES CONTROL] +disable=all + +# Enable message and code: +# anomalous-backslash-in-string - W1401 +# assignment-from-none - W1111 +# dangerous-default-value - W0102 +# duplicate-key - W0109 +# pointless-statement - W0104 +# pointless-string-statement - W0105 +# print-statement - E1601 +# redundant-keyword-arg - E1124 +# reimported - W0404 +# relative-import - W0403 +# return-in-init - E0101 +# too-few-format-args - E1306 +# unreachable - W0101 + +enable=anomalous-backslash-in-string, + assignment-from-none, + dangerous-default-value, + duplicate-key, + missing-import-error, + missing-manifest-dependency, + pointless-statement, + pointless-string-statement, + print-statement, + redundant-keyword-arg, + reimported, + relative-import, + return-in-init, + too-few-format-args, + unreachable, + +[REPORTS] +msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} +output-format=colorized +files-output=no +reports=no +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) +comment=no + +[FORMAT] +indent-string=' ' + +[SIMILARITIES] +ignore-comments=yes +ignore-docstrings=yes + +[MISCELLANEOUS] +notes= diff --git a/mqt/cfg/pylint_beta.cfg b/mqt/cfg/pylint_beta.cfg new file mode 100644 index 000000000..929948ef9 --- /dev/null +++ b/mqt/cfg/pylint_beta.cfg @@ -0,0 +1,75 @@ +# This file adds a list of messages that when activated by another +# pylint configuration file will display them without affecting your build status. + +# Beta message and code: +# api-one-deprecated - W8104 +# api-one-multi-together - W8101 +# attribute-deprecated - W8105 +# class-camelcase - C8104 +# create-user-wo-reset-password - W7905 +# copy-wo-api-one - W8102 +# dangerous-filter-wo-user - W7901 +# deprecated-module - W0402 +# duplicate-id-csv - W7906 +# duplicate-xml-fields - W7907 +# duplicate-xml-record-id - W7902 +# incoherent-interpreter-exec-perm - W8201 +# invalid-commit - E8102 +# javascript-lint - W7903 +# manifest-deprecated-key - C8103 +# method-compute - C8108 +# method-inverse - C8110 +# method-required-super - W8106 +# method-search - C8109 +# missing-newline-extrafiles - W7908 +# missing-readme - C7902 +# no-utf8-coding-comment - C8201 +# openerp-exception-warning - R8101 +# redundant-modulename-xml - W7909 +# rst-syntax-error - E7901 +# sql-injection - E8103 +# too-complex - C0901 +# translation-field - W8103 +# translation-required - C8107 +# use-vim-comment - W8202 +# wrong-tabs-instead-of-spaces - W7910 +# xml-syntax-error - E7902 + +[MESSAGES CONTROL] +enabled2beta=api-one-deprecated, + api-one-multi-together, + attribute-deprecated, + class-camelcase, + create-user-wo-reset-password, + consider-merging-classes-inherited, + copy-wo-api-one, + dangerous-filter-wo-user, + dangerous-view-replace-wo-priority, + deprecated-module, + duplicate-id-csv, + duplicate-xml-fields, + duplicate-xml-record-id, + file-not-used, + incoherent-interpreter-exec-perm, + invalid-commit, + javascript-lint, + manifest-deprecated-key, + method-compute, + method-inverse, + method-required-super, + method-search, + missing-newline-extrafiles, + missing-readme, + no-utf8-coding-comment, + odoo-addons-relative-import, + old-api7-method-defined, + openerp-exception-warning, + redundant-modulename-xml, + rst-syntax-error, + sql-injection, + too-complex, + translation-field, + translation-required, + use-vim-comment, + wrong-tabs-instead-of-spaces, + xml-syntax-error, diff --git a/mqt/cfg/pylint_exclude_61.cfg b/mqt/cfg/pylint_exclude_61.cfg new file mode 100644 index 000000000..f65109223 --- /dev/null +++ b/mqt/cfg/pylint_exclude_61.cfg @@ -0,0 +1,27 @@ +# This config file not is a real pylint config file. +# This file overwrite params of base template. + +[ODOOLINT] +manifest_deprecated_keys= + +[MESSAGES CONTROL] +# Disable message and code: +# attribute-deprecated - W8105 +# copy-wo-api-one - W8102 +# class-camelcase - C8104 +# missing-readme - C7902 +# openerp-exception-warning - R8101 + +disable=api-one-deprecated, + attribute-deprecated, + copy-wo-api-one, + class-camelcase, + missing-readme, + old-api7-method-defined, + openerp-exception-warning, + missing-import-error, + missing-manifest-dependency, + +[IMPORTS] +deprecated-modules=pdb,pudb,ipdb + diff --git a/mqt/cfg/pylint_exclude_70.cfg b/mqt/cfg/pylint_exclude_70.cfg new file mode 100644 index 000000000..7d8f69fd8 --- /dev/null +++ b/mqt/cfg/pylint_exclude_70.cfg @@ -0,0 +1,27 @@ +# This config file not is a real pylint config file. +# This file overwrite params of base template. + +[ODOOLINT] +manifest_deprecated_keys= + +[MESSAGES CONTROL] +# Disable message and code: +# attribute-deprecated - W8105 +# copy-wo-api-one - W8102 +# class-camelcase - C8104 +# missing-readme - C7902 +# openerp-exception-warning - R8101 + +disable=api-one-deprecated, + attribute-deprecated, + copy-wo-api-one, + class-camelcase, + missing-readme, + old-api7-method-defined, + openerp-exception-warning, + missing-import-error, + missing-manifest-dependency + +[IMPORTS] +deprecated-modules=pdb,pudb,ipdb + diff --git a/mqt/cfg/pylint_pr.cfg b/mqt/cfg/pylint_pr.cfg new file mode 100644 index 000000000..d10bebbad --- /dev/null +++ b/mqt/cfg/pylint_pr.cfg @@ -0,0 +1,123 @@ +# Used to check enabled messages on this file just in modules +# changed in a pull request of the project. +# The result affect your build status. + +[MASTER] +profile=no +ignore=CVS,.git,scenarios,.bzr +persistent=yes +cache-size=500 + +[ODOOLINT] +readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst" +manifest_required_author="Odoo Community Association (OCA)" +manifest_required_keys=license +manifest_deprecated_keys=description,active + +[MESSAGES CONTROL] +disable=all + +# Enable message and code: +# api-one-deprecated - W8104 +# api-one-multi-together - W8101 +# attribute-deprecated - W8105 +# class-camelcase - C8104 +# create-user-wo-reset-password - W7905 +# copy-wo-api-one - W8102 +# dangerous-filter-wo-user - W7901 +# deprecated-module - W0402 +# duplicate-id-csv - W7906 +# duplicate-xml-fields - W7907 +# duplicate-xml-record-id - W7902 +# eval-used - W0123 +# eval-referenced - W8111 +# incoherent-interpreter-exec-perm - W8201 +# invalid-commit - E8102 +# javascript-lint - W7903 +# manifest-author-string - E8101 +# manifest-deprecated-key - C8103 +# manifest-required-author - C8101 +# manifest-required-key - C8102 +# manifest-version-format - C8106 +# method-compute - C8108 +# method-inverse - C8110 +# method-required-super - W8106 +# method-search - C8109 +# missing-newline-extrafiles - W7908 +# missing-readme - C7902 +# no-utf8-coding-comment - C8201 +# openerp-exception-warning - R8101 +# redundant-modulename-xml - W7909 +# rst-syntax-error - E7901 +# sql-injection - E8103 +# too-complex - C0901 +# translation-field - W8103 +# translation-required - C8107 +# use-vim-comment - W8202 +# wrong-tabs-instead-of-spaces - W7910 +# xml-syntax-error - E7902 + +enable=api-one-deprecated, + api-one-multi-together, + attribute-deprecated, + class-camelcase, + create-user-wo-reset-password, + consider-merging-classes-inherited, + copy-wo-api-one, + dangerous-filter-wo-user, + dangerous-view-replace-wo-priority, + deprecated-module, + duplicate-id-csv, + duplicate-xml-fields, + duplicate-xml-record-id, + file-not-used, + eval-used, + eval-referenced, + incoherent-interpreter-exec-perm, + invalid-commit, + javascript-lint, + manifest-author-string, + manifest-deprecated-key, + manifest-required-author, + manifest-required-key, + manifest-version-format, + method-compute, + method-inverse, + method-required-super, + method-search, + missing-newline-extrafiles, + missing-readme, + no-utf8-coding-comment, + odoo-addons-relative-import, + old-api7-method-defined, + openerp-exception-warning, + redundant-modulename-xml, + rst-syntax-error, + sql-injection, + too-complex, + translation-field, + translation-required, + use-vim-comment, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + +[REPORTS] +msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} +output-format=colorized +files-output=no +reports=no +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) +comment=no + +[FORMAT] +indent-string=' ' + +[SIMILARITIES] +ignore-comments=yes +ignore-docstrings=yes + +[MISCELLANEOUS] +notes= + +[IMPORTS] +deprecated-modules=pdb,pudb,ipdb,openerp.osv diff --git a/mqt/cli.py b/mqt/cli.py new file mode 100644 index 000000000..825f535f1 --- /dev/null +++ b/mqt/cli.py @@ -0,0 +1,48 @@ +import os +import click +import sys + +from .lints import flake +from .lints import run + +CLICK_DIR = click.Path(exists=True, dir_okay=True, resolve_path=True) + + +@click.group() +def cli(): + """Maintainer Quality tools from OCA helper scripts.""" + click.echo('Running OCA Maintainer quality tools.!') + + +@cli.command() +@click.option('paths', '--path', + envvar='BUILD_DIR', + multiple=True, + type=CLICK_DIR, + required=True, + default=[os.getcwd()], + help="Addons paths to check pylint") +@click.option('--config-file', '-c', + type=click.File('r', lazy=True), + help="Pylint config file") +@click.option('--sys-paths', '-sys-path', + envvar='PYTHONPATH', + multiple=True, + type=CLICK_DIR, + help="Additional paths to append in sys path.") +@click.option('--extra-params', '-extra-param', + multiple=True, + help="Extra pylint params to append in pylint command") +@click.option('--msgs-no-count', '-msgs-no-count', + multiple=True, + help="List of messages that will not add to the failure count.") +def lint(paths, config_file, msgs_no_count=None, + sys_paths=None, extra_params=None): + """Test the pylint an odoo-addons folder.""" + run(list(paths), cfg=config_file, sys_paths=sys_paths, extra_params=extra_params) + + +@cli.command() +def flake8(): + """Test the Flake8 an odoo-addons folder.""" + sys.exit(flake()) \ No newline at end of file diff --git a/mqt/getaddons.py b/mqt/getaddons.py new file mode 100755 index 000000000..f0fa55119 --- /dev/null +++ b/mqt/getaddons.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +""" +Usage: get-addons [-m] path1 [path2 ...] +Given a list of paths, finds and returns a list of valid addons paths. +With -m flag, will return a list of modules names instead. +""" + +import ast +import os +import sys + +from .git_run import GitRun + +MANIFEST_FILES = [ + '__manifest__.py', + '__odoo__.py', + '__openerp__.py', + '__terp__.py', +] + + +def is_module(path): + """return False if the path doesn't contain an odoo module, and the full + path to the module manifest otherwise""" + + if not os.path.isdir(path): + return False + files = os.listdir(path) + filtered = [x for x in files if x in (MANIFEST_FILES + ['__init__.py'])] + if len(filtered) == 2 and '__init__.py' in filtered: + return os.path.join( + path, next(x for x in filtered if x != '__init__.py')) + else: + return False + + +def is_installable_module(path): + """return False if the path doesn't contain an installable odoo module, + and the full path to the module manifest otherwise""" + manifest_path = is_module(path) + if manifest_path: + manifest = ast.literal_eval(open(manifest_path).read()) + if manifest.get('installable', True): + return manifest_path + return False + + +def get_modules(path): + + # Avoid empty basename when path ends with slash + if not os.path.basename(path): + path = os.path.dirname(path) + + res = [] + if os.path.isdir(path): + res = [x for x in os.listdir(path) + if is_installable_module(os.path.join(path, x))] + return res + + +def is_addons(path): + res = get_modules(path) != [] + return res + + +def get_addons(path): + if not os.path.exists(path): + return [] + if is_addons(path): + res = [path] + else: + res = [os.path.join(path, x) + for x in sorted(os.listdir(path)) + if is_addons(os.path.join(path, x))] + return res + + +def get_modules_changed(path, ref='HEAD'): + '''Get modules changed from git diff-index {ref} + + :param path: String path of git repo + :param ref: branch or remote/branch or sha to compare + :return: List of paths of modules changed + ''' + git_run_obj = GitRun(os.path.join(path, '.git')) + if ref != 'HEAD': + fetch_ref = ref + if ':' not in fetch_ref: + # to force create branch + fetch_ref += ':' + fetch_ref + git_run_obj.run(['fetch'] + fetch_ref.split('/', 1)) + items_changed = git_run_obj.get_items_changed(ref) + folders_changed = set([ + item_changed.split('/')[0] + for item_changed in items_changed + if '/' in item_changed] + ) + modules = set(get_modules(path)) + modules_changed = list(modules & folders_changed) + modules_changed_path = [ + os.path.join(path, module_changed) + for module_changed in modules_changed] + return modules_changed_path + + +def main(argv=None): + if argv is None: + argv = sys.argv + params = argv[1:] + if not params: + print(__doc__) + return 1 + + list_modules = False + exclude_modules = [] + + while params and params[0].startswith('-'): + param = params.pop(0) + if param == '-m': + list_modules = True + if param == '-e': + exclude_modules = [x for x in params.pop(0).split(',')] + + func = get_modules if list_modules else get_addons + lists = [func(x) for x in params] + res = [x for l in lists for x in l] # flatten list of lists + if exclude_modules: + res = [x for x in res if x not in exclude_modules] + print(','.join(res)) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/mqt/git_run.py b/mqt/git_run.py new file mode 100644 index 000000000..27d3c0d8b --- /dev/null +++ b/mqt/git_run.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +import subprocess +from six import string_types + + +class GitRun(object): + + def __init__(self, repo_path, debug=False): + self.repo_path = repo_path + self.debug = debug + + def run(self, command): + """Execute git command in bash + + :param list command: Git cmd to execute in self.repo_path + :type command: list + :return: String output of command executed. + """ + cmd = ['git', '--git-dir=' + self.repo_path] + command + print(cmd if self.debug else '') + try: + res = subprocess.check_output(cmd) + except subprocess.CalledProcessError: + res = None + if isinstance(res, string_types): + res = res.strip('\n') + return res + + def get_items_changed(self, base_ref='HEAD'): + """Get name of items changed in self.repo_path + This is a wrapper method of git command: + + git diff-index --name-only --cached {base_ref} + + :param base_ref: String of branch or sha base. e.g. "master" or + "SHA_NUMBER" + :type base_ref: str + :return: List of name of items changed + """ + command = ['diff-index', '--name-only', + '--cached', base_ref] + res = self.run(command) + items = res.decode('UTF-8').split('\n') if res else [] + return items + + def get_branch_name(self): + """Get branch name + :return: String with name of current branch name""" + command = ['rev-parse', '--abbrev-ref', 'HEAD'] + res = self.run(command) + return res diff --git a/mqt/helpers.py b/mqt/helpers.py new file mode 100644 index 000000000..04a9ec2b1 --- /dev/null +++ b/mqt/helpers.py @@ -0,0 +1,36 @@ +# coding: utf-8 +""" +helpers shared by the various QA tools +""" + + +RED = "\033[1;31m" +GREEN = "\033[1;32m" +YELLOW = "\033[1;33m" +YELLOW_LIGHT = "\033[33m" +CLEAR = "\033[0;m" + + +def colorized(text, color): + return '\n'.join( + map(lambda line: color + line + CLEAR, text.split('\n'))) + + +def green(text): + return colorized(text, GREEN) + + +def yellow(text): + return colorized(text, YELLOW) + + +def red(text): + return colorized(text, RED) + + +def yellow_light(text): + return colorized(text, YELLOW_LIGHT) + + +fail_msg = red("FAIL") +success_msg = green("Success") diff --git a/mqt/lints.py b/mqt/lints.py new file mode 100755 index 000000000..fd5649119 --- /dev/null +++ b/mqt/lints.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import re +import sys +import subprocess +from os.path import join +from os.path import isfile +import pylint.lint + +try: + import ConfigParser +except ImportError: + import configparser as ConfigParser + +from . import getaddons +from . import helpers +from .getaddons import get_modules_changed +from .git_run import GitRun +from .mqt import Mqt + + +class Lints(Mqt): + """This class runs `pylint` with plugins and special configuration files. + Use the following types of pylint configuration files: + + 1. Global + 2. Pull request + 3. Version + 4. Beta + + 1. Global + + Used to check enabled messages on this file in all modules of the project. + The result affect your build status. **File name:** `pylint.cfg` + + 2. Pull request + + Used to check enabled messages on this file just in modules changed in a + pull request of the project. The result affects your build status. **File + name:** `pylint_pr.cfg` + + 3. Version Exclusions + + Used to disable the previously enabled messages of `global` or + `pull request` configuration files based on `VERSION` variable + environment. **File name:** `pylint_exclude_{VERSION}.cfg` + + 4. Beta + + Used to add the previously enabled messages in `global` or `pull + request` configuration files that you want to display, without + affecting your build status. **File name:** `pylint_beta.cfg` + """ + def __init__(self): + self.expected_errors = int(os.environ.get('PYLINT_EXPECTED_ERRORS', 0)) + self.message_no_count = self.get_msg_no_count() + super(Lints, self).__init__() + + def get_msg_no_count(self): + return 'c18,p23,k66' + + def get_count_fails(linter_stats, msgs_no_count=None): + """Verify the dictionary statistics to get number of errors. + + :param linter_stats: Dict of type pylint.lint.Run().linter.stats + :param msgs_no_count: List of messages that will not add to the failure count. + :return: Integer with quantity of fails found. + """ + return sum([ + linter_stats['by_msg'][msg] + for msg in linter_stats['by_msg'] + if msg not in msgs_no_count]) + + def run_pylint(self, paths, cfg, + beta_msgs=None, + sys_paths=None, + extra_params=None): + """Execute pylint command from original python library + + :param paths: List of paths of python modules to check pylint + :param cfg: String name of pylint configuration file + :param sys_paths: List of paths to append to sys path + :param extra_params: List of parameters extra to append + in pylint command + :return: Dict with python linter stats + """ + if sys_paths is None: + sys_paths = [] + if extra_params is None: + extra_params = [] + if not cfg: + cfg = join(mqt.path, 'cfg/pylint.cfg') + sys.path.extend(sys_paths) + cmd = ['--rcfile=' + cfg] + cmd.extend(extra_params) + sub_paths = self.get_sub_paths(paths) + if not sub_paths: + raise UserWarning("Python modules not found in paths" + " {paths} ".format(paths=paths)) + cmd.extend(sub_paths) + pylint_res = pylint.lint.Run(cmd, exit=False) + return pylint_res.linter.stats + + def run(self, paths, config_file, msgs_no_count=None, sys_paths=None, + extra_params=None): + try: + fname = config_file if config_file else False + stats = self.run_pylint( + list(paths), + cfg=fname, + sys_paths=sys_paths, + extra_params=extra_params + ) + count_fails = self.get_count_fails(stats, list(msgs_no_count)) + except UserWarning: + count_fails = -1 + return count_fails + + def get_extra_params(self, odoo_version): + """Get extra pylint params by odoo version + Transform a seudo-pylint-conf to params, + it to overwrite base-pylint-conf values. + Use a seudo-inherit of configuration file. + To avoid have a 2 config files (stable and pr-conf) by each odoo-version + Example: + + pylint_master.conf + pylint_master_pr.conf + pylint_90.conf + pylint_90_pr.conf + pylint_80.conf + pylint_80_pr.conf + pylint_70.conf + pylint_70_pr.conf + pylint_61.conf + pylint_61_pr.conf + ... and new future versions. + + If you need add a new conventions in all versions + you will need change all pr files or stables files. + + + With this method you can use: + + pylint_lastest.conf + pylint_lastest_pr.conf + pylint_disabling_70.conf <- Overwrite params of pylint_lastest*.conf + pylint_disabling_61.conf <- Overwrite params of pylint_lastest*.conf + + If you need add a new conventions in all versions you will need change just + pylint_lastest_pr.conf or pylint_lastest.conf, similar to inherit. + + :param odoo_version: String with name of version of odoo + :return: List of extra pylint params + """ + odoo_version = odoo_version.replace('.', '') + version_cfg = join( + self.path, + 'cfg/pylint_exclude_{odoo_version}.cfg'.format( + odoo_version=odoo_version)) + params = [] + if isfile(version_cfg): + config = ConfigParser.ConfigParser() + config.readfp(open(version_cfg)) + for section in config.sections(): + for option, value in config.items(section): + params.extend(['--' + option, value]) + return params + + def get_beta_msgs(self): + """Get beta msgs from beta.cfg file + + :return: List of strings with beta message names""" + beta_cfg = join( + self.path, + 'cfg/pylint_beta.cfg') + if not isfile(beta_cfg): + return [] + config = ConfigParser.ConfigParser() + config.readfp(open(beta_cfg)) + return [ + msg.strip() + for msg in config.get('MESSAGES CONTROL', 'enabled2beta').split(',') + if msg.strip()] + def do_magic_params(self): + + def do_magic_shell(self): # TODO: rename this variable this is a temporal + # name + # TODO: I just fix here to make it work, tons of refactor possibles + # here. + git_work_dir = (self.BUILD_DIR + if not isinstance(self.BUILD_DIR, list) else False) + extra_params_cmd = [ + '--sys-paths', + join(self.path, 'pylint_deprecated_modules'), + '--extra-params', '--load-plugins=pylint_odoo',] + exit_status = 0 + if not self.VERSION and git_work_dir: + repo_path = join(git_work_dir, '.git') + branch_name = GitRun(repo_path).get_branch_name() + version = (branch_name.replace('_', '-').split('-')[:1] + if branch_name else False) + version = self.VERSION[0] if self.VERSION and len(self.VERSION) else None + if version: + extra_params = self.get_extra_params(version) + for extra_param in extra_params: + extra_params_cmd.extend(['--extra-params', extra_param]) + is_version_number = re.match(r'\d+\.\d+', version) + if is_version_number: + extra_params_cmd.extend([ + '--extra-params', '--valid_odoo_versions=%s' % version]) + beta_msgs = self.get_beta_msgs() + [extra_params_cmd.extend(['--msgs-no-count', beta_msg]) + for beta_msg in beta_msgs] + + # Look for an environment variable + # whose value is the name of a proper configuration file for pylint + # (this file will then be expected to be found in the 'cfg/' folder). + # If such an environment variable is not found, + # it defaults to the standard configuration file. + pylint_rcfile = join(self.path, 'cfg', self.PYLINT_CONFIG_FILE) + count_errors = self.run(self.BUILD_DIR, pylint_rcfile) + pylint_rcfile_pr = join(self.path, 'cfg', "pylint_pr.cfg") + + if self.PULL_REQUEST and self.BRANCH and git_work_dir: + if self.BRANCH != 'HEAD': + self.BRANCH = 'origin/' + self.BRANCH + modules_changed = get_modules_changed(git_work_dir, self.BRANCH) + if modules_changed and count_errors >= 0: + print( + helpers.green('Start lint check just in modules changed')) + modules_changed_cmd = [] + for module_changed in modules_changed: + modules_changed_cmd.extend(['--path', module_changed,]) + cmd = (["--config-file=" + pylint_rcfile_pr, + ] + modules_changed_cmd + extra_params_cmd) + pr_errors = self.run(cmd, standalone_mode=False) + if pr_errors: + print(helpers.yellow( + "Found {pr_errors} errors".format(pr_errors=pr_errors) + + " in modules changed." + )) + if pr_errors < 0: + count_errors = pr_errors + else: + count_errors += pr_errors + + if count_errors == -1: + print(helpers.yellow('Python modules not found')) + elif count_errors != self.expected_errors: + print(helpers.red("pylint expected errors {expected_errors}, " + "found {number_errors}!".format( + expected_errors=self.expected_errors, + number_errors=count_errors))) + exit_status = 1 + if beta_msgs and count_errors >= 0: + print(helpers.green( + "\nNext checks are still in beta " + "they won't affect your build status for now: " + '\n' + ', '.join(sorted(beta_msgs)))) + return exit_status + + def flake(self): + """Just run Flake8 per directory""" + status = 0 + for addon in getaddons.get_modules(os.path.abspath('.')): + fname = join(self.path, 'cfg', 'flake8__init__.cfg') + status += subprocess.call(['flake8', '--config', fname, addon]) + fname = join(self.path, 'cfg', 'flake8.cfg') + status += subprocess.call(['flake8', '--config', fname, addon]) + + return 0 if status == 0 else 1 + + output = pylint.lint.Run(params) + res = analize(output) + return res + diff --git a/mqt/mqt.py b/mqt/mqt.py new file mode 100755 index 000000000..751954858 --- /dev/null +++ b/mqt/mqt.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import ( + absolute_import, division, print_function, with_statement, + unicode_literals +) +import ast +from os import environ +from os import getcwd +from os import listdir +from os.path import basename +from os.path import dirname +from os.path import isdir +from os.path import isfile +from os.path import join +from os.path import realpath + + +TRANSIFEX_USER = 'transbot@odoo-community.org' + + +class Mqt(object): + """Set the environment elements to enable the set of such elements in the + mqt package and avoid to pass them trough all methods, here also you + will find the generic methods that will .""" + + BUILD_DIR = False + """TODO""" + PULL_REQUEST = False + """Is this being run in a Pull request?, it is CI dependant, then to set + this parameter you need to enable this vrariable from your CI itself. + + i.e: On .travis.yml file: + PULL_REQUEST = $TRAVIS_PULL_REQUEST""" + LINT_CHECK = 0 + """TODO""" + TESTS = 0 + """TODO""" + TRANSIFEX = 0 + """TODO""" + WEBLATE = 0 + """TODO""" + TRAVIS_REPO_SLUG = True + """TODO""" + TRANSIFEX_USER = False + """TODO""" + BRANCH = False + """Branch name taken from the CI itself, necessary for some automations + related with the branch name. + + i.e: On .travis.yml file: + PULL_REQUEST = $TRAVIS_BRANCH + """ + VERSION = False + """Which is the odoo version where the set of odoo modules will be tested + on""" + PYLINT_CONFIG_FILE = False + """Look for an environment variable whose value is the name of a proper + configuration file for pylint # (this file will then be expected to be + found in the 'cfg/' folder). If such an environment variable is not found, + it defaults to the standard configuration file.""" + PYLINT_EXPECTED_ERRORS = False + """Errors that you can live with but you want to explicitly silence them + without change your .cfg configuration file, generally usefull on the WiP + environments""" + path = False + sub_paths = [] + + def __init__(self): + self.LINT_CHECK = environ.get('LINT_CHECK') or self.LINT_CHECK + self.TESTS = environ.get('TESTS') or self.TESTS + self.WEBLATE = environ.get('WEBLATE') + self.TRAVIS_REPO_SLUG = environ.get('TRAVIS_REPO_SLUG') + self.TRANSIFEX_USER = environ.get('TRANSIFEX_USER') == TRANSIFEX_USER + self.BUILD_DIR = environ.get('BUILD_DIR', [getcwd()]) + self.PULL_REQUEST = environ.get('PULL_REQUEST') + self.BRANCH = environ.get('BRANCH') + self.VERSION = environ.get('VERSION') + self.PYLINT_CONFIG_FILE = environ.get('PYLINT_CONFIG_FILE', + 'pylint_pr.cfg') + self.PYLINT_EXPECTED_ERRORS = environ.get('PYLINT_EXPECTED_ERRORS') + self.path = dirname(realpath(__file__)) + self.MANIFEST_FILES = [ + '__manifest__.py', + '__odoo__.py', + '__openerp__.py', + '__terp__.py', + ] + + def get_modules(self, path): + if not basename(path): + path = dirname(path) + + res = [] + if isdir(path): + res = [x for x in listdir(path) + if self.is_installable(join(path, x))] + return res + + def is_installable(self, path): + manifest_path = self.is_module(path) + if manifest_path: + manifest = ast.literal_eval(open(manifest_path).read()) + if manifest.get('installable', True): + return manifest_path + return False + + def is_addons(self, path): + res = self.get_modules(path) != [] + return res + + def is_module(self, path): + """Given a path that **maybe** is a module check if it is actually. + + :param path: Path with the *possible* module directory + :type path: str + :return: + """ + + if not isdir(path): + return False + files = listdir(path) + filtered = [x for x in files + if x in (self.MANIFEST_FILES + ['__init__.py'])] + if not len(filtered) == 2 and '__init__.py' in filtered: + return False + return join(path, next(x for x in filtered if x != '__init__.py')) + + def get_sub_paths(self, paths): + """Get list of subdirectories if `__init__.py` file not exists in root + path then get subdirectories. + + Why? More info `Python `_. + + :param paths: List of directories that you want to get the + subdirectories from. + :type paths: list + :return: Return list of paths with subdirectories with actual python + modules. + :rtype: list + """ + subpaths = [] + for path in paths: + if not isfile(join(path, '__init__.py')): + subpaths.extend( + [join(path, item) + for item in listdir(path) + if isfile(join(path, item, '__init__.py')) and + (not self.is_module(join(path, item)) or + self.is_installable_module(join(path, item)))]) + else: + if not self.is_module(path) or \ + self.is_installable_module(path): + subpaths.append(path) + return subpaths diff --git a/mqt/testsuite/__init__.py b/mqt/testsuite/__init__.py new file mode 100755 index 000000000..d679325af --- /dev/null +++ b/mqt/testsuite/__init__.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Tests for mqt.""" +from __future__ import ( + absolute_import, division, print_function, with_statement, + unicode_literals +) + +from .._compat import text_type, string_types, PY2, reraise + +try: + import unittest2 as unittest +except ImportError: # Python 2.7 + import unittest +import logging +import sys +import pkgutil + + +class ImportStringError(ImportError): + """Provides information about a failed :func:`import_string` attempt.""" + + #: String in dotted notation that failed to be imported. + import_name = None + #: Wrapped exception. + exception = None + + def __init__(self, import_name, exception): + self.import_name = import_name + self.exception = exception + + msg = ( + 'import_string() failed for %r. Possible reasons are:\n\n' + '- missing __init__.py in a package;\n' + '- package or module path not included in sys.path;\n' + '- duplicated package or module name taking precedence in ' + 'sys.path;\n' + '- missing module, class, function or variable;\n\n' + 'Debugged import:\n\n%s\n\n' + 'Original exception:\n\n%s: %s') + + name = '' + tracked = [] + for part in import_name.replace(':', '.').split('.'): + name += (name and '.') + part + imported = import_string(name, silent=True) + if imported: + tracked.append((name, getattr(imported, '__file__', None))) + else: + track = ['- %r found in %r.' % (n, i) for n, i in tracked] + track.append('- %r not found.' % name) + msg = msg % (import_name, '\n'.join(track), + exception.__class__.__name__, str(exception)) + break + + ImportError.__init__(self, msg) + + def __repr__(self): + return '<%s(%r, %r)>' % (self.__class__.__name__, self.import_name, + self.exception) + + +def import_string(import_name, silent=False): + """Imports an object based on a string. This is useful if you want to + use import paths as endpoints or something similar. An import path can + be specified either in dotted notation (``xml.sax.saxutils.escape``) + or with a colon as object delimiter (``xml.sax.saxutils:escape``). + + If `silent` is True the return value will be `None` if the import fails. + + :param import_name: the dotted name for the object to import. + :param silent: if set to `True` import errors are ignored and + `None` is returned instead. + :return: imported object + """ + #XXX: py3 review needed + assert isinstance(import_name, string_types) + # force the import name to automatically convert to strings + import_name = str(import_name) + try: + if ':' in import_name: + module, obj = import_name.split(':', 1) + elif '.' in import_name: + module, obj = import_name.rsplit('.', 1) + else: + return __import__(import_name) + # __import__ is not able to handle unicode strings in the fromlist + # if the module is a package + if PY2 and isinstance(obj, unicode): + obj = obj.encode('utf-8') + try: + return getattr(__import__(module, None, None, [obj]), obj) + except (ImportError, AttributeError): + # support importing modules not yet set up by the parent module + # (or package for that matter) + modname = module + '.' + obj + __import__(modname) + return sys.modules[modname] + except ImportError as e: + if not silent: + reraise( + ImportStringError, + ImportStringError(import_name, e), + sys.exc_info()[2]) + + +def find_modules(import_path, include_packages=False, recursive=False): + """Find all the modules below a package. This can be useful to + automatically import all views / controllers so that their metaclasses / + function decorators have a chance to register themselves on the + application. + + Packages are not returned unless `include_packages` is `True`. This can + also recursively list modules but in that case it will import all the + packages to get the correct load path of that module. + + :param import_name: the dotted name for the package to find child modules. + :param include_packages: set to `True` if packages should be returned, too. + :param recursive: set to `True` if recursion should happen. + :return: generator + """ + module = import_string(import_path) + path = getattr(module, '__path__', None) + if path is None: + raise ValueError('%r is not a package' % import_path) + basename = module.__name__ + '.' + for importer, modname, ispkg in pkgutil.iter_modules(path): + modname = basename + modname + if ispkg: + if include_packages: + yield modname + if recursive: + for item in find_modules(modname, include_packages, True): + yield item + else: + yield modname + + +def iter_suites(package): + """Yields all testsuites.""" + for module in find_modules(package, include_packages=True): + mod = __import__(module, fromlist=['*']) + if hasattr(mod, 'suite'): + yield mod.suite() + + +def find_all_tests(suite): + """Yields all the tests and their names from a given suite.""" + suites = [suite] + while suites: + s = suites.pop() + try: + suites.extend(s) + except TypeError: + yield s, '%s.%s.%s' % ( + s.__class__.__module__, + s.__class__.__name__, + s._testMethodName + ) + + +class BetterLoader(unittest.TestLoader): + """A nicer loader that solves two problems. First of all we are setting + up tests from different sources and we're doing this programmatically + which breaks the default loading logic so this is required anyways. + Secondly this loader has a nicer interpolation for test names than the + default one so you can just do ``run-tests.py ViewTestCase`` and it + will work. + """ + + def getRootSuite(self): + return suite() + + def loadTestsFromName(self, name, module=None): + root = self.getRootSuite() + if name == 'suite': + return root + + all_tests = [] + for testcase, testname in find_all_tests(root): + if testname == name or \ + testname.endswith('.' + name) or \ + ('.' + name + '.') in testname or \ + testname.startswith(name + '.'): + all_tests.append(testcase) + + if not all_tests: + raise LookupError('could not find test case for "%s"' % name) + + if len(all_tests) == 1: + return all_tests[0] + rv = unittest.TestSuite() + for test in all_tests: + rv.addTest(test) + return rv + + +def suite(): + """A testsuite that has all the Flask tests. You can use this + function to integrate the Flask tests into your own testsuite + in case you want to test that monkeypatches to Flask do not + break it. + """ + suite = unittest.TestSuite() + for other_suite in iter_suites(__name__): + suite.addTest(other_suite) + return suite + + +def main(): + """Runs the testsuite as command line application.""" + try: + unittest.main(testLoader=BetterLoader(), defaultTest='suite') + except Exception: + import sys + import traceback + traceback.print_exc() + sys.exit(1) diff --git a/mqt/testsuite/helpers.py b/mqt/testsuite/helpers.py new file mode 100644 index 000000000..f00216b2e --- /dev/null +++ b/mqt/testsuite/helpers.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +"""Tests for mqt.""" + +from __future__ import ( + absolute_import, division, print_function, with_statement, + unicode_literals +) + +import os +import sys + +from contextlib import contextmanager + +from .._compat import StringIO + + +def add_to_path(path): + """Adds an entry to sys.path if it's not already there. This does + not append it but moves it to the front so that we can be sure it + is loaded. + """ + if not os.path.isdir(path): + raise RuntimeError('Tried to add nonexisting path') + + def _samefile(x, y): + if x == y: + return True + try: + return os.path.samefile(x, y) + except (IOError, OSError, AttributeError): + # Windows has no samefile + return False + sys.path[:] = [x for x in sys.path if not _samefile(path, x)] + sys.path.insert(0, path) + + +def setup_path(): + script_path = os.path.join( + os.path.dirname(__file__), os.pardir, + ) + add_to_path(script_path) + + +def get_datapath(filename): + + return os.path.join( + os.path.dirname(__file__), 'fixtures', filename + ) + + +@contextmanager +def captureStdErr(command, *args, **kwargs): + out, sys.stderr = sys.stderr, StringIO() + command(*args, **kwargs) + sys.stderr.seek(0) + yield sys.stderr.read() + sys.stderr = out + + +@contextmanager +def captureStdOut(command, *args, **kwargs): + out, sys.stdout = sys.stderr, StringIO() + command(*args, **kwargs) + sys.stdout.seek(0) + yield sys.stdout.read() + sys.stdout = out diff --git a/mqt/testsuite/mqt.py b/mqt/testsuite/mqt.py new file mode 100755 index 000000000..02984fb26 --- /dev/null +++ b/mqt/testsuite/mqt.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Tests for `mqt` module.""" + +from __future__ import ( + absolute_import, division, print_function, with_statement, + unicode_literals +) + +from .. import mqt + +from . import unittest + + +class MqtTestCase(unittest.TestCase): + + def setUp(self): + pass + + def test_something(self): + pass + + def test_something_docstring(self): + """Here is a sample test with a docstring. Hey.""" + self.assertTrue(True) + + def tearDown(self): + pass + + +def suite(): + from .helpers import setup_path + setup_path() + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(MqtTestCase)) + return suite diff --git a/requirements.txt b/requirements.txt index ce4b61e17..720f9d1c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,9 @@ pyopenssl>=16.2.0 pyyaml coveralls unittest2 +click +pylint +flake8 # transifex requirements polib diff --git a/run-tests.py b/run-tests.py new file mode 100755 index 000000000..b18209d5d --- /dev/null +++ b/run-tests.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from mqt.testsuite import main +main() diff --git a/sample_files/.gitignore b/sample_files/.gitignore index f7f8a408b..a12368887 100644 --- a/sample_files/.gitignore +++ b/sample_files/.gitignore @@ -8,6 +8,7 @@ __pycache__/ # Distribution / packaging .Python env/ +venv/ bin/ build/ develop-eggs/ diff --git a/setup.py b/setup.py new file mode 100755 index 000000000..977259c2e --- /dev/null +++ b/setup.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys + + +try: + from setuptools import setup, find_packages +except ImportError: + from distutils.core import setup, find_packages + +about = {} +with open("mqt/__about__.py") as fp: + exec(fp.read(), about) + +with open('requirements.txt') as f: + install_reqs = [line for line in f.read().split('\n') if line] + tests_reqs = [] + +if sys.version_info < (2, 7): + install_reqs += ['argparse'] + tests_reqs += ['unittest2'] + +if sys.argv[-1] == 'publish': + os.system('python setup.py sdist upload') + sys.exit() + +if sys.argv[-1] == 'info': + for k, v in about.items(): + print('%s: %s' % (k, v)) + sys.exit() + +readme = open('README.rst').read() +history = open('CHANGES').read().replace('.. :changelog:', '') +console_scripts = open('SCRIPTS').read() + +setup( + name=about['__title__'], + version=about['__version__'], + description=about['__description__'], + long_description=readme + '\n\n' + history, + author=about['__author__'], + author_email=about['__email__'], + url='https://github.com/oca/mqt', + package_dir={'cfdilib': + 'cfdilib'}, + packages=find_packages(exclude=[ + 'docs', + 'travis', + 'tests', + 'git', + 'cfg', + 'sample_files', + ]), + include_package_data=True, + install_requires=install_reqs, + tests_require=tests_reqs, + license=about['__license__'], + keywords=about['__title__'], + zip_safe=False, + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + "Programming Language :: Python :: 2", + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + ], + test_suite='mqt.testsuite', + py_modules=['mqt'], + entry_points=console_scripts, +) diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..7752318a3 --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] +envlist = py26, py27, py33 + +[testenv] +setenv = + PYTHONPATH = {toxinidir}:{toxinidir}/mqt +commands = python setup.py test +deps = + -r{toxinidir}/requirements.txt diff --git a/travis/test_pylint b/travis/test_pylint index dec119585..0bb1ff053 100755 --- a/travis/test_pylint +++ b/travis/test_pylint @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - - """This script runs `pylint` with plugins and special configuration files. Use the following types of pylint configuration files: