* Significant refactoring: lugito wrapped into a class with common methods, IRC and Launchpad communications separated as connectors with a separate webhook module. * Some unit-tests added * Added functionality for ircbot to respond to diff links and info as well as extracting anchor links from tasks / diffs e.g. D15#167 * Built into a python package structure Still Requires: * Improved test coverage * Package documentation * Additional functionality for other phabricator appspull/1/head
parent
165c866a5d
commit
f327136c34
@ -1,3 +1,27 @@
|
||||
{
|
||||
"phabricator.uri" : "https://phab.lubuntu.me/"
|
||||
"hosts": {
|
||||
"http://phab.lubuntu.me/": {
|
||||
"token": ""
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"default": "http://phab.lubunutu.me/api/"
|
||||
},
|
||||
"HMAC": i{
|
||||
"irc": "",
|
||||
},
|
||||
"irc": {
|
||||
"host": "irc.freenode.net",
|
||||
"port": "6697",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"channel": "#lubuntu-devel"
|
||||
},
|
||||
"launchpad": {
|
||||
"application": "lugito",
|
||||
"staging": "production",
|
||||
"version": "devel",
|
||||
"supported_versions": ["Cosmic", "Bionic", "Xenial", "Trusty"]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
[run]
|
||||
omit =
|
||||
tests/*
|
||||
*/test*.py
|
||||
*/migrations/*
|
||||
*/wsgi.py
|
||||
*/apps.py
|
||||
*_version.py
|
||||
parallel = true
|
||||
|
||||
[paths]
|
||||
source =
|
||||
lugito
|
||||
|
||||
[report]
|
||||
precision = 2
|
||||
include =
|
||||
lugito/*.py
|
||||
exclude_lines =
|
||||
def __str__
|
||||
pragma: no cover
|
||||
|
||||
[html]
|
||||
directory = coverage_html_report
|
@ -0,0 +1,22 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
max_line_length=80
|
||||
|
||||
[*.bat]
|
||||
indent_style = tab
|
||||
end_of_line = crlf
|
||||
|
||||
[LICENSE]
|
||||
insert_final_newline = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
@ -0,0 +1 @@
|
||||
lugito/_version.py export-subst
|
@ -0,0 +1,13 @@
|
||||
=======
|
||||
Credits
|
||||
=======
|
||||
|
||||
Development Lead
|
||||
----------------
|
||||
|
||||
* Ben Johnston <bjohnston@neomailbox.net>
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
None yet. Why not be the first?
|
@ -0,0 +1,128 @@
|
||||
.. highlight:: shell
|
||||
|
||||
============
|
||||
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/doc-E-brown/lugito/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" and "help
|
||||
wanted" is open to whoever wants to implement it.
|
||||
|
||||
Implement Features
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Look through the GitHub issues for features. Anything tagged with "enhancement"
|
||||
and "help wanted" is open to whoever wants to implement it.
|
||||
|
||||
Write Documentation
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
lugito could always use more documentation, whether as part of the
|
||||
official lugito 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/doc-E-brown/lugito/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 `lugito` for local development.
|
||||
|
||||
1. Fork the `lugito` repo on GitHub.
|
||||
2. Clone your fork locally::
|
||||
|
||||
$ git clone git@github.com:your_name_here/lugito.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 lugito
|
||||
$ cd lugito/
|
||||
$ 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 lugito tests
|
||||
$ python setup.py test or 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.7, 3.4, 3.5 and 3.6, and for PyPy. Check
|
||||
https://travis-ci.org/doc-E-brown/lugito/pull_requests
|
||||
and make sure that the tests pass for all supported Python versions.
|
||||
|
||||
Tips
|
||||
----
|
||||
|
||||
To run a subset of tests::
|
||||
|
||||
$ py.test tests.test_lugito
|
||||
|
||||
|
||||
Deploying
|
||||
---------
|
||||
|
||||
A reminder for the maintainers on how to deploy.
|
||||
Make sure all your changes are committed (including an entry in HISTORY.rst).
|
||||
Then run::
|
||||
|
||||
$ bumpversion patch # possible: major / minor / patch
|
||||
$ git push
|
||||
$ git push --tags
|
||||
|
||||
Travis will then deploy to PyPI if tests pass.
|
@ -0,0 +1,8 @@
|
||||
=======
|
||||
History
|
||||
=======
|
||||
|
||||
0.1.0 (2018-11-07)
|
||||
------------------
|
||||
|
||||
* First release on PyPI.
|
@ -0,0 +1,13 @@
|
||||
include AUTHORS.rst
|
||||
include CONTRIBUTING.rst
|
||||
include HISTORY.rst
|
||||
include LICENSE
|
||||
include README.rst
|
||||
|
||||
recursive-include tests *
|
||||
recursive-exclude * __pycache__
|
||||
recursive-exclude * *.py[co]
|
||||
|
||||
recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif
|
||||
include versioneer.py
|
||||
include lugito/_version.py
|
@ -0,0 +1,88 @@
|
||||
.PHONY: clean clean-test clean-pyc clean-build docs help
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
define BROWSER_PYSCRIPT
|
||||
import os, webbrowser, sys
|
||||
|
||||
try:
|
||||
from urllib import pathname2url
|
||||
except:
|
||||
from urllib.request import pathname2url
|
||||
|
||||
webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
|
||||
endef
|
||||
export BROWSER_PYSCRIPT
|
||||
|
||||
define PRINT_HELP_PYSCRIPT
|
||||
import re, sys
|
||||
|
||||
for line in sys.stdin:
|
||||
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
|
||||
if match:
|
||||
target, help = match.groups()
|
||||
print("%-20s %s" % (target, help))
|
||||
endef
|
||||
export PRINT_HELP_PYSCRIPT
|
||||
|
||||
BROWSER := python -c "$$BROWSER_PYSCRIPT"
|
||||
|
||||
help:
|
||||
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
|
||||
|
||||
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
|
||||
|
||||
clean-build: ## remove build artifacts
|
||||
rm -fr build/
|
||||
rm -fr dist/
|
||||
rm -fr .eggs/
|
||||
find . -name '*.egg-info' -exec rm -fr {} +
|
||||
find . -name '*.egg' -exec rm -f {} +
|
||||
|
||||
clean-pyc: ## remove Python file artifacts
|
||||
find . -name '*.pyc' -exec rm -f {} +
|
||||
find . -name '*.pyo' -exec rm -f {} +
|
||||
find . -name '*~' -exec rm -f {} +
|
||||
find . -name '__pycache__' -exec rm -fr {} +
|
||||
|
||||
clean-test: ## remove test and coverage artifacts
|
||||
rm -fr .tox/
|
||||
rm -f .coverage
|
||||
rm -fr htmlcov/
|
||||
rm -fr .pytest_cache
|
||||
|
||||
lint: ## check style with flake8
|
||||
flake8 lugito tests
|
||||
|
||||
test: ## run tests quickly with the default Python
|
||||
py.test
|
||||
|
||||
test-all: ## run tests on every Python version with tox
|
||||
tox
|
||||
|
||||
coverage: ## check code coverage quickly with the default Python
|
||||
coverage run --source lugito -m pytest
|
||||
coverage report -m
|
||||
coverage html
|
||||
$(BROWSER) htmlcov/index.html
|
||||
|
||||
docs: ## generate Sphinx HTML documentation, including API docs
|
||||
rm -f docs/lugito.rst
|
||||
rm -f docs/modules.rst
|
||||
sphinx-apidoc -o docs/ lugito
|
||||
$(MAKE) -C docs clean
|
||||
$(MAKE) -C docs html
|
||||
$(BROWSER) docs/_build/html/index.html
|
||||
|
||||
servedocs: docs ## compile the docs watching for changes
|
||||
watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D .
|
||||
|
||||
release: dist ## package and upload a release
|
||||
twine upload dist/*
|
||||
|
||||
dist: clean ## builds source and wheel package
|
||||
python setup.py sdist
|
||||
python setup.py bdist_wheel
|
||||
ls -l dist
|
||||
|
||||
install: clean ## install the package to the active Python's site-packages
|
||||
python setup.py install
|
@ -0,0 +1,37 @@
|
||||
======
|
||||
lugito
|
||||
======
|
||||
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/lugito.svg
|
||||
:target: https://pypi.python.org/pypi/lugito
|
||||
|
||||
.. image:: https://img.shields.io/travis/doc-E-brown/lugito.svg
|
||||
:target: https://travis-ci.org/doc-E-brown/lugito
|
||||
|
||||
.. image:: https://readthedocs.org/projects/lugito/badge/?version=latest
|
||||
:target: https://lugito.readthedocs.io/en/latest/?badge=latest
|
||||
:alt: Documentation Status
|
||||
|
||||
|
||||
|
||||
|
||||
Python Boilerplate contains all the boilerplate you need to create a Python package.
|
||||
|
||||
|
||||
* Free software: BSD license
|
||||
* Documentation: https://lugito.readthedocs.io.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* TODO
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
|
||||
|
||||
.. _Cookiecutter: https://github.com/audreyr/cookiecutter
|
||||
.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage
|
@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = python -msphinx
|
||||
SPHINXPROJ = lugito
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
@ -0,0 +1 @@
|
||||
.. include:: ../AUTHORS.rst
|
@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# lugito documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Jun 9 13:47:02 2017.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# 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.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
import lugito
|
||||
|
||||
# -- 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']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'lugito'
|
||||
copyright = u"2018, Ben Johnston"
|
||||
author = u"Ben Johnston"
|
||||
|
||||
# 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 = lugito.__version__
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = lugito.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = 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 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']
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ---------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'lugitodoc'
|
||||
|
||||
|
||||
# -- 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': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'lugito.tex',
|
||||
u'lugito Documentation',
|
||||
u'Ben Johnston', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for manual page output ------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'lugito',
|
||||
u'lugito Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- 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 = [
|
||||
(master_doc, 'lugito',
|
||||
u'lugito Documentation',
|
||||
author,
|
||||
'lugito',
|
||||
'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
.. include:: ../CONTRIBUTING.rst
|
@ -0,0 +1 @@
|
||||
.. include:: ../HISTORY.rst
|
@ -0,0 +1,20 @@
|
||||
Welcome to lugito's documentation!
|
||||
======================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
readme
|
||||
installation
|
||||
usage
|
||||
modules
|
||||
contributing
|
||||
authors
|
||||
history
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
@ -0,0 +1,51 @@
|
||||
.. highlight:: shell
|
||||
|
||||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
|
||||
Stable release
|
||||
--------------
|
||||
|
||||
To install lugito, run this command in your terminal:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install lugito
|
||||
|
||||
This is the preferred method to install lugito, as it will always install the most recent stable release.
|
||||
|
||||
If you don't have `pip`_ installed, this `Python installation guide`_ can guide
|
||||
you through the process.
|
||||
|
||||
.. _pip: https://pip.pypa.io
|
||||
.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/
|
||||
|
||||
|
||||
From sources
|
||||
------------
|
||||
|
||||
The sources for lugito can be downloaded from the `Github repo`_.
|
||||
|
||||
You can either clone the public repository:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ git clone git://github.com/doc-E-brown/lugito
|
||||
|
||||
Or download the `tarball`_:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ curl -OL https://github.com/doc-E-brown/lugito/tarball/master
|
||||
|
||||
Once you have a copy of the source, you can install it with:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
|
||||
.. _Github repo: https://github.com/doc-E-brown/lugito
|
||||
.. _tarball: https://github.com/doc-E-brown/lugito/tarball/master
|
@ -0,0 +1,36 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=python -msphinx
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
set SPHINXPROJ=lugito
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The Sphinx module was not found. Make sure you have Sphinx installed,
|
||||
echo.then set the SPHINXBUILD environment variable to point to the full
|
||||
echo.path of the 'sphinx-build' executable. Alternatively you may add the
|
||||
echo.Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
@ -0,0 +1 @@
|
||||
.. include:: ../README.rst
|
@ -0,0 +1,7 @@
|
||||
=====
|
||||
Usage
|
||||
=====
|
||||
|
||||
To use lugito in a project::
|
||||
|
||||
import lugito
|
@ -0,0 +1,16 @@
|
||||
from lugito.lugito import (
|
||||
Lugito,
|
||||
)
|
||||
|
||||
from lugito.connectors.irc import (
|
||||
IRCConnector,
|
||||
)
|
||||
|
||||
from lugito.connectors.launchpad import (
|
||||
LPConnectook,
|
||||
)
|
||||
|
||||
|
||||
from ._version import get_versions
|
||||
__version__ = get_versions()['version']
|
||||
del get_versions
|
@ -0,0 +1,520 @@
|
||||
|
||||
# This file helps to compute a version number in source trees obtained from
|
||||
# git-archive tarball (such as those provided by githubs download-from-tag
|
||||
# feature). Distribution tarballs (built by setup.py sdist) and build
|
||||
# directories (produced by setup.py build) will contain a much shorter file
|
||||
# that just contains the computed version number.
|
||||
|
||||
# This file is released into the public domain. Generated by
|
||||
# versioneer-0.18 (https://github.com/warner/python-versioneer)
|
||||
|
||||
"""Git implementation of _version.py."""
|
||||
|
||||
import errno
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def get_keywords():
|
||||
"""Get the keywords needed to look up the version information."""
|
||||
# these strings will be replaced by git during git-archive.
|
||||
# setup.py/versioneer.py will grep for the variable names, so they must
|
||||
# each be defined on a line of their own. _version.py will just call
|
||||
# get_keywords().
|
||||
git_refnames = "$Format:%d$"
|
||||
git_full = "$Format:%H$"
|
||||
git_date = "$Format:%ci$"
|
||||
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
|
||||
return keywords
|
||||
|
||||
|
||||
class VersioneerConfig:
|
||||
"""Container for Versioneer configuration parameters."""
|
||||
|
||||
|
||||
def get_config():
|
||||
"""Create, populate and return the VersioneerConfig() object."""
|
||||
# these strings are filled in when 'setup.py versioneer' creates
|
||||
# _version.py
|
||||
cfg = VersioneerConfig()
|
||||
cfg.VCS = "git"
|
||||
cfg.style = "pep440"
|
||||
cfg.tag_prefix = ""
|
||||
cfg.parentdir_prefix = "lugito-"
|
||||
cfg.versionfile_source = "lugito/_version.py"
|
||||
cfg.verbose = False
|
||||
return cfg
|
||||
|
||||
|
||||
class NotThisMethod(Exception):
|
||||
"""Exception raised if a method is not valid for the current scenario."""
|
||||
|
||||
|
||||
LONG_VERSION_PY = {}
|
||||
HANDLERS = {}
|
||||
|
||||
|
||||
def register_vcs_handler(vcs, method): # decorator
|
||||
"""Decorator to mark a method as the handler for a particular VCS."""
|
||||
def decorate(f):
|
||||
"""Store f in HANDLERS[vcs][method]."""
|
||||
if vcs not in HANDLERS:
|
||||
HANDLERS[vcs] = {}
|
||||
HANDLERS[vcs][method] = f
|
||||
return f
|
||||
return decorate
|
||||
|
||||
|
||||
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
|
||||
env=None):
|
||||
"""Call the given command(s)."""
|
||||
assert isinstance(commands, list)
|
||||
p = None
|
||||
for c in commands:
|
||||
try:
|
||||
dispcmd = str([c] + args)
|
||||
# remember shell=False, so use git.cmd on windows, not just git
|
||||
p = subprocess.Popen([c] + args, cwd=cwd, env=env,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=(subprocess.PIPE if hide_stderr
|
||||
else None))
|
||||
break
|
||||
except EnvironmentError:
|
||||
e = sys.exc_info()[1]
|
||||
if e.errno == errno.ENOENT:
|
||||
continue
|
||||
if verbose:
|
||||
print("unable to run %s" % dispcmd)
|
||||
print(e)
|
||||
return None, None
|
||||
else:
|
||||
if verbose:
|
||||
print("unable to find command, tried %s" % (commands,))
|
||||
return None, None
|
||||
stdout = p.communicate()[0].strip()
|
||||
if sys.version_info[0] >= 3:
|
||||
stdout = stdout.decode()
|
||||
if p.returncode != 0:
|
||||
if verbose:
|
||||
print("unable to run %s (error)" % dispcmd)
|
||||
print("stdout was %s" % stdout)
|
||||
return None, p.returncode
|
||||
return stdout, p.returncode
|
||||
|
||||
|
||||
def versions_from_parentdir(parentdir_prefix, root, verbose):
|
||||
"""Try to determine the version from the parent directory name.
|
||||
|
||||
Source tarballs conventionally unpack into a directory that includes both
|
||||
the project name and a version string. We will also support searching up
|
||||
two directory levels for an appropriately named parent directory
|
||||
"""
|
||||
rootdirs = []
|
||||
|
||||
for i in range(3):
|
||||
dirname = os.path.basename(root)
|
||||
if dirname.startswith(parentdir_prefix):
|
||||
return {"version": dirname[len(parentdir_prefix):],
|
||||
"full-revisionid": None,
|
||||
"dirty": False, "error": None, "date": None}
|
||||
else:
|
||||
rootdirs.append(root)
|
||||
root = os.path.dirname(root) # up a level
|
||||
|
||||
if verbose:
|
||||
print("Tried directories %s but none started with prefix %s" %
|
||||
(str(rootdirs), parentdir_prefix))
|
||||
raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
|
||||
|
||||
|
||||
@register_vcs_handler("git", "get_keywords")
|
||||
def git_get_keywords(versionfile_abs):
|
||||
"""Extract version information from the given file."""
|
||||
# the code embedded in _version.py can just fetch the value of these
|
||||
# keywords. When used from setup.py, we don't want to import _version.py,
|
||||
# so we do it with a regexp instead. This function is not used from
|
||||
# _version.py.
|
||||
keywords = {}
|
||||
try:
|
||||
f = open(versionfile_abs, "r")
|
||||
for line in f.readlines():
|
||||
if line.strip().startswith("git_refnames ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
keywords["refnames"] = mo.group(1)
|
||||
if line.strip().startswith("git_full ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
keywords["full"] = mo.group(1)
|
||||
if line.strip().startswith("git_date ="):
|
||||
mo = re.search(r'=\s*"(.*)"', line)
|
||||
if mo:
|
||||
keywords["date"] = mo.group(1)
|
||||
f.close()
|
||||
except EnvironmentError:
|
||||
pass
|
||||
return keywords
|
||||
|
||||
|
||||
@register_vcs_handler("git", "keywords")
|
||||
def git_versions_from_keywords(keywords, tag_prefix, verbose):
|
||||
"""Get version information from git keywords."""
|
||||
if not keywords:
|
||||
raise NotThisMethod("no keywords at all, weird")
|
||||
date = keywords.get("date")
|
||||
if date is not None:
|
||||
# git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
|
||||
# datestamp. However we prefer "%ci" (which expands to an "ISO-8601
|
||||
# -like" string, which we must then edit to make compliant), because
|
||||
# it's been around since git-1.5.3, and it's too difficult to
|
||||
# discover which version we're using, or to work around using an
|
||||
# older one.
|
||||
date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
|
||||
refnames = keywords["refnames"].strip()
|
||||
if refnames.startswith("$Format"):
|
||||
if verbose:
|
||||
print("keywords are unexpanded, not using")
|
||||
raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
|
||||
refs = set([r.strip() for r in refnames.strip("()").split(",")])
|
||||
# starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
|
||||
# just "foo-1.0". If we see a "tag: " prefix, prefer those.
|
||||
TAG = "tag: "
|
||||
tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
|
||||
if not tags:
|
||||
# Either we're using git < 1.8.3, or there really are no tags. We use
|
||||
# a heuristic: assume all version tags have a digit. The old git %d
|
||||
# expansion behaves like git log --decorate=short and strips out the
|
||||
# refs/heads/ and refs/tags/ prefixes that would let us distinguish
|
||||
# between branches and tags. By ignoring refnames without digits, we
|
||||
# filter out many common branch names like "release" and
|
||||
# "stabilization", as well as "HEAD" and "master".
|
||||
tags = set([r for r in refs if re.search(r'\d', r)])
|
||||
if verbose:
|
||||
print("discarding '%s', no digits" % ",".join(refs - tags))
|
||||
if verbose:
|
||||
print("likely tags: %s" % ",".join(sorted(tags)))
|
||||
for ref in sorted(tags):
|
||||
# sorting will prefer e.g. "2.0" over "2.0rc1"
|
||||
if ref.startswith(tag_prefix):
|
||||
r = ref[len(tag_prefix):]
|
||||
if verbose:
|
||||
print("picking %s" % r)
|
||||
return {"version": r,
|
||||
"full-revisionid": keywords["full"].strip(),
|
||||
"dirty": False, "error": None,
|
||||
"date": date}
|
||||
# no suitable tags, so version is "0+unknown", but full hex is still there
|
||||
if verbose:
|
||||
print("no suitable tags, using unknown + full revision id")
|
||||
return {"version": "0+unknown",
|
||||
"full-revisionid": keywords["full"].strip(),
|
||||
"dirty": False, "error": "no suitable tags", "date": None}
|
||||
|
||||
|
||||
@register_vcs_handler("git", "pieces_from_vcs")
|
||||
def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
|
||||
"""Get version from 'git describe' in the root of the source tree.
|
||||
|
||||
This only gets called if the git-archive 'subst' keywords were *not*
|
||||
expanded, and _version.py hasn't already been rewritten with a short
|
||||
version string, meaning we're inside a checked out source tree.
|
||||
"""
|
||||
GITS = ["git"]
|
||||
if sys.platform == "win32":
|
||||
GITS = ["git.cmd", "git.exe"]
|
||||
|
||||
out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
|
||||
hide_stderr=True)
|
||||
if rc != 0:
|
||||
if verbose:
|
||||
print("Directory %s not under git control" % root)
|
||||
raise NotThisMethod("'git rev-parse --git-dir' returned error")
|
||||
|
||||
# if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
|
||||
# if there isn't one, this yields HEX[-dirty] (no NUM)
|
||||
describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
|
||||
"--always", "--long",
|
||||
"--match", "%s*" % tag_prefix],
|
||||
cwd=root)
|
||||
# --long was added in git-1.5.5
|
||||
if describe_out is None:
|
||||
raise NotThisMethod("'git describe' failed")
|
||||
describe_out = describe_out.strip()
|
||||
full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
|
||||
if full_out is None:
|
||||
raise NotThisMethod("'git rev-parse' failed")
|
||||
full_out = full_out.strip()
|
||||
|
||||
pieces = {}
|
||||
pieces["long"] = full_out
|
||||
pieces["short"] = full_out[:7] # maybe improved later
|
||||
pieces["error"] = None
|
||||
|
||||
# parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
|
||||
# TAG might have hyphens.
|
||||
git_describe = describe_out
|
||||
|
||||
# look for -dirty suffix
|
||||
dirty = git_describe.endswith("-dirty")
|
||||
pieces["dirty"] = dirty
|
||||
if dirty:
|
||||
git_describe = git_describe[:git_describe.rindex("-dirty")]
|
||||
|
||||
# now we have TAG-NUM-gHEX or HEX
|
||||
|
||||
if "-" in git_describe:
|
||||
# TAG-NUM-gHEX
|
||||
mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
|
||||
if not mo:
|
||||
# unparseable. Maybe git-describe is misbehaving?
|
||||
pieces["error"] = ("unable to parse git-describe output: '%s'"
|
||||
% describe_out)
|
||||
return pieces
|
||||
|
||||
# tag
|
||||
full_tag = mo.group(1)
|
||||
if not full_tag.startswith(tag_prefix):
|
||||
if verbose:
|
||||
fmt = "tag '%s' doesn't start with prefix '%s'"
|
||||
print(fmt % (full_tag, tag_prefix))
|
||||
pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
|
||||
% (full_tag, tag_prefix))
|
||||
return pieces
|
||||
pieces["closest-tag"] = full_tag[len(tag_prefix):]
|
||||
|
||||
# distance: number of commits since tag
|
||||
pieces["distance"] = int(mo.group(2))
|
||||
|
||||
# commit: short hex revision ID
|
||||
pieces["short"] = mo.group(3)
|
||||
|
||||
else:
|
||||
# HEX: no tags
|
||||
pieces["closest-tag"] = None
|
||||
count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
|
||||
cwd=root)
|
||||
pieces["distance"] = int(count_out) # total number of commits
|
||||
|
||||
# commit date: see ISO-8601 comment in git_versions_from_keywords()
|
||||
date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
|
||||
cwd=root)[0].strip()
|
||||
pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
|
||||
|
||||
return pieces
|
||||
|
||||
|
||||
def plus_or_dot(pieces):
|
||||
"""Return a + if we don't already have one, else return a ."""
|
||||
if "+" in pieces.get("closest-tag", ""):
|
||||
return "."
|
||||
return "+"
|
||||
|
||||
|
||||
def render_pep440(pieces):
|
||||
"""Build up version string, with post-release "local version identifier".
|
||||
|
||||
Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
|
||||
get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
|
||||
|
||||
Exceptions:
|
||||
1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
|
||||
"""
|
||||
if pieces["closest-tag"]:
|
||||
rendered = pieces["closest-tag"]
|
||||
if pieces["distance"] or pieces["dirty"]:
|
||||
rendered += plus_or_dot(pieces)
|
||||
rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
|
||||
if pieces["dirty"]:
|
||||
rendered += ".dirty"
|
||||
else:
|
||||
# exception #1
|
||||
rendered = "0+untagged.%d.g%s" % (pieces["distance"],
|
||||
pieces["short"])
|
||||
if pieces["dirty"]:
|
||||
rendered += ".dirty"
|
||||
return rendered
|
||||
|
||||
|
||||
def render_pep440_pre(pieces):
|
||||
"""TAG[.post.devDISTANCE] -- No -dirty.
|
||||
|
||||
Exceptions:
|
||||
1: no tags. 0.post.devDISTANCE
|
||||
"""
|
||||
if pieces["closest-tag"]:
|
||||
rendered = pieces["closest-tag"]
|
||||
if pieces["distance"]:
|
||||
rendered += ".post.dev%d" % pieces["distance"]
|
||||
else:
|
||||
# exception #1
|
||||
rendered = "0.post.dev%d" % pieces["distance"]
|
||||
return rendered
|
||||
|
||||
|
||||
def render_pep440_post(pieces):
|
||||
"""TAG[.postDISTANCE[.dev0]+gHEX] .
|
||||
|
||||
The ".dev0" means dirty. Note that .dev0 sorts backwards
|
||||
(a dirty tree will appear "older" than the corresponding clean one),
|
||||
but you shouldn't be releasing software with -dirty anyways.
|
||||
|
||||
Exceptions:
|
||||
1: no tags. 0.postDISTANCE[.dev0]
|
||||
"""
|
||||
if pieces["closest-tag"]:
|
||||
rendered = pieces["closest-tag"]
|
||||
if pieces["distance"] or pieces["dirty"]:
|
||||
rendered += ".post%d" % pieces["distance"]
|
||||
if pieces["dirty"]:
|
||||
rendered += ".dev0"
|
||||
rendered += plus_or_dot(pieces)
|
||||
rendered += "g%s" % pieces["short"]
|
||||
else:
|
||||
# exception #1
|
||||
rendered = "0.post%d" % pieces["distance"]
|
||||
if pieces["dirty"]:
|
||||
rendered += ".dev0"
|
||||
rendered += "+g%s" % pieces["short"]
|
||||
return rendered
|
||||
|
||||
|
||||
def render_pep440_old(pieces):
|
||||
"""TAG[.postDISTANCE[.dev0]] .
|
||||
|
||||
The ".dev0" means dirty.
|
||||
|
||||
Eexceptions:
|
||||
1: no tags. 0.postDISTANCE[.dev0]
|
||||
"""
|
||||
if pieces["closest-tag"]:
|
||||
rendered = pieces["closest-tag"]
|
||||
if pieces["distance"] or pieces["dirty"]:
|
||||
rendered += ".post%d" % pieces["distance"]
|
||||
if pieces["dirty"]:
|
||||
rendered += ".dev0"
|
||||
else:
|
||||
# exception #1
|
||||
rendered = "0.post%d" % pieces["distance"]
|
||||
if pieces["dirty"]:
|
||||
rendered += ".dev0"
|
||||
return rendered
|
||||
|
||||
|
||||
def render_git_describe(pieces):
|
||||
"""TAG[-DISTANCE-gHEX][-dirty].
|
||||
|
||||
Like 'git describe --tags --dirty --always'.
|
||||
|
||||
Exceptions:
|
||||
1: no tags. HEX[-dirty] (note: no 'g' prefix)
|
||||
"""
|
||||
if pieces["closest-tag"]:
|
||||
rendered = pieces["closest-tag"]
|
||||
if pieces["distance"]:
|
||||
rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
|
||||
else:
|
||||
# exception #1
|
||||
rendered = pieces["short"]
|
||||
if pieces["dirty"]:
|
||||
rendered += "-dirty"
|
||||
return rendered
|
||||
|
||||
|
||||
def render_git_describe_long(pieces):
|
||||
"""TAG-DISTANCE-gHEX[-dirty].
|
||||
|
||||
Like 'git describe --tags --dirty --always -long'.
|
||||
The distance/hash is unconditional.
|
||||
|
||||
Exceptions:
|
||||
1: no tags. HEX[-dirty] (note: no 'g' prefix)
|
||||
"""
|
||||
if pieces["closest-tag"]:
|
||||
rendered = pieces["closest-tag"]
|
||||
rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
|
||||
else:
|
||||
# exception #1
|
||||
rendered = pieces["short"]
|
||||
if pieces["dirty"]:
|
||||
rendered += "-dirty"
|
||||
return rendered
|
||||
|
||||
|
||||
def render(pieces, style):
|
||||
"""Render the given version pieces into the requested style."""
|
||||
if pieces["error"]:
|
||||
return {"version": "unknown",
|
||||
"full-revisionid": pieces.get("long"),
|
||||
"dirty": None,
|
||||
"error": pieces["error"],
|
||||
"date": None}
|
||||
|
||||
if not style or style == "default":
|
||||
style = "pep440" # the default
|
||||
|
||||
if style == "pep440":
|
||||
rendered = render_pep440(pieces)
|
||||
elif style == "pep440-pre":
|
||||
rendered = render_pep440_pre(pieces)
|
||||
elif style == "pep440-post":
|
||||
rendered = render_pep440_post(pieces)
|
||||
elif style == "pep440-old":
|
||||
rendered = render_pep440_old(pieces)
|
||||
elif style == "git-describe":
|
||||
rendered = render_git_describe(pieces)
|
||||
elif style == "git-describe-long":
|
||||
rendered = render_git_describe_long(pieces)
|
||||
else:
|
||||
raise ValueError("unknown style '%s'" % style)
|
||||
|
||||
return {"version": rendered, "full-revisionid": pieces["long"],
|
||||
"dirty": pieces["dirty"], "error": None,
|
||||
"date": pieces.get("date")}
|
||||
|
||||
|
||||
def get_versions():
|
||||
"""Get version information or return default if unable to do so."""
|
||||
# I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
|
||||
# __file__, we can work backwards from there to the root. Some
|
||||
# py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
|
||||
# case we can only use expanded keywords.
|
||||
|
||||
cfg = get_config()
|
||||
verbose = cfg.verbose
|
||||
|
||||
try:
|
||||
return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
|
||||
verbose)
|
||||
except NotThisMethod:
|
||||
pass
|
||||
|
||||
try:
|
||||
root = os.path.realpath(__file__)
|
||||
# versionfile_source is the relative path from the top of the source
|
||||
# tree (where the .git directory might live) to this file. Invert
|
||||
# this to find the root from __file__.
|
||||
for i in cfg.versionfile_source.split('/'):
|
||||
root = os.path.dirname(root)
|
||||
except NameError:
|
||||
return {"version": "0+unknown", "full-revisionid": None,
|
||||
"dirty": None,
|
||||
"error": "unable to find root of source tree",
|
||||
"date": None}
|
||||
|
||||
try:
|
||||
pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
|
||||
return render(pieces, cfg.style)
|
||||
except NotThisMethod:
|
||||
pass
|
||||
|
||||
try:
|
||||
if cfg.parentdir_prefix:
|
||||
return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
|
||||
except NotThisMethod:
|
||||
pass
|
||||
|
||||
return {"version": "0+unknown", "full-revisionid": None,
|
||||
"dirty": None,
|
||||
"error": "unable to compute version", "date": None}
|
@ -0,0 +1,19 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# S.D.G
|
||||
|
||||
"""Doc string for module
|
||||
|
||||
|
||||
|
||||
:author: Ben Johnston
|
||||
|
||||
"""
|
||||
|
||||
# Imports
|
||||
|
||||
|
||||
from lugito.webhooks import run
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
@ -0,0 +1,3 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# S.D.G
|
@ -0,0 +1,239 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# S.D.G
|
||||
|
||||
"""
|
||||
:mod:`lugito.connectors.irc`
|
||||
======================================
|
||||
|
||||
Define an irc connector class
|
||||
|
||||
.. currentmodule:: lugito.connectors.irc
|
||||
"""
|
||||
|
||||
# Imports
|
||||
import ssl
|
||||
import http
|
||||
import socket
|
||||
import logging
|
||||
import threading
|
||||
import phabricator
|
||||
from time import sleep
|
||||
|
||||
|
||||
class IRCConnector(object):
|
||||
|
||||
def __init__(self, log_level=logging.DEBUG):
|
||||
|
||||
# IRC info
|
||||
# Read the configuration out of the .arcconfig file
|
||||
self.host = phabricator.ARCRC['irc']['host']
|
||||
self.port = int(phabricator.ARCRC['irc']['port'])
|
||||
self.username = phabricator.ARCRC['irc']['username']
|
||||
self.password = phabricator.ARCRC['irc']['password']
|
||||
self.channel = phabricator.ARCRC['irc']['channel']
|
||||
|
||||
# Phabricator info
|
||||
self.phab = phabricator.Phabricator()
|
||||
self.phab_host = phabricator.ARCRC['config']['default'].replace(
|
||||
'api/', '')
|
||||
|
||||
self.logger = logging.getLogger('lugito.connector.IRCConnector')
|
||||
|
||||
# Add log level
|
||||
ch = logging.StreamHandler()
|
||||
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
|
||||
self.logger.addHandler(ch)
|
||||
self.logger.setLevel(log_level)
|
||||
|
||||
|
||||
def _send_raw(self, message):
|
||||
"""Low level send"""
|
||||
|
||||
self.conn.send(message.encode('utf-8'))
|
||||
|
||||
|
||||
def _socket_conn(self):
|
||||
self.conn = ssl.wrap_socket(
|
||||
socket.socket(socket.AF_INET, socket.SOCK_STREAM))
|
||||
self.conn.connect((self.host, self.port))
|
||||
|
||||
|
||||
def connect(self):
|
||||
"""Connect"""
|
||||
|
||||
self._socket_conn()
|
||||
|
||||
setup = False
|
||||
usersuffix = 0
|
||||
self.logger.info("Connecting to IRC.")
|
||||
|
||||
while not setup:
|
||||
response = self.conn.recv(512).decode("utf-8")
|
||||
self.logger.debug(response)
|
||||
|
||||
if "No Ident response" in response:
|
||||
self._send_raw("NICK {}\r\n".format(self.username))
|
||||
self._send_raw("USER {} * * :{}\r\n".format(
|
||||
self.username, self.username))
|
||||
self._send_raw("PRIVMSG nickserv :identify {} {}\r\n".format(
|
||||
self.username, self.password))
|
||||
|
||||
if "You are now identified" in response:
|
||||
sleep(5)
|
||||
self._send_raw("JOIN {}\r\n".format(self.channel))
|
||||
|
||||
if "477" in response:
|
||||
sleep(5)
|
||||
self._send_raw("JOIN {}\r\n".format(self.channel))
|
||||
|
||||
if "433" in response:
|
||||
usersuffix = usersuffix + 1
|
||||
self.username = self.username + str(usersuffix)
|
||||
self._send_raw("NICK {}\r\n".format(self.username))
|
||||
self._send_raw("USER {} * * :{}\r\n".format(
|
||||
self.username, self.username))
|
||||
|
||||
if "PING" in response:
|
||||
self._send_raw("PONG :{}\r\n".format(response.split(":")[1]))
|
||||
|
||||
if "366" in response:
|
||||
setup = True
|
||||
|
||||
self.logger.info("Successfully connected to the IRC server.")
|
||||
|
||||
def send_notice(self, message):
|
||||
self._send_raw("NOTICE {} :{}\r\n".format(self.channel, message))
|
||||
|
||||
def send(self, objectstr, who, body, link):
|
||||
"""Send a formatted message"""
|
||||
|
||||
# e.g. [T31: Better IRC integration]
|
||||
message = "\x033[\x03\x0313" + objectstr + "\x03\x033]\x03 "
|
||||
# e.g. tsimonq2 (Simon Quigley)
|
||||
message = message + "\x0315" + who + "\x03 "
|
||||
# e.g. commented on the task:
|
||||
message = message + body + ": "
|
||||
# e.g. https://phab.lubuntu.me/T40#779
|
||||
message = message + "\x032" + link + "\x03"
|
||||
# Make sure we can debug this if it goes haywire
|
||||
self.logger.debug(message)
|
||||
# Sleep for a fifth of a second, so when we have a bunch of messages we have a buffer
|
||||
sleep(0.2)
|
||||
# Aaaaand, send it off!
|
||||
self.send_notice(message)
|
||||
|
||||
def gettaskinfo(self, task):
|
||||
|
||||
sendmessage = ""
|
||||
|
||||
# Strip out anchor link
|
||||
# This will prevent invalid task / diff references
|
||||
anchor = None
|
||||
if '#' in task:
|
||||
task, anchor = task.split('#')
|
||||
|
||||
try:
|
||||
# We only need the task number.
|
||||
if len(task.split("T")) > 1:
|
||||
taskinfo = self.phab.maniphest.info(
|
||||
task_id=int(task.split("T")[1]))
|
||||
|
||||
# or the diff number
|
||||
elif len(task.split("D")) > 1:
|
||||
taskinfo = self.phab.differential.query(
|
||||
ids=[int(task.split("D")[1])])[0]
|
||||
taskinfo['priorityColor'] = None
|
||||
|
||||
sendmessage += "\x033[\x03"
|
||||
|
||||
# The color of the priority text should correspond to its value.
|
||||
color = taskinfo["priorityColor"]
|
||||
if color == "violet":
|
||||
sendmessage += "\x036Needs Triage"
|
||||
elif color == "pink":
|
||||
sendmessage += "\x035Unbreak Now!"
|
||||
elif color == "red":
|
||||
sendmessage += "\x034High"
|
||||
elif color == "orange":
|
||||
sendmessage += "\x037Medium"
|
||||
elif color == "yellow":
|
||||
sendmessage += "\x038Low"
|
||||
elif color == "sky":
|
||||
sendmessage += "\x037Wishlist"
|
||||
|
||||
# Put the task status in the message.
|
||||
if color is not None:
|
||||
sendmessage += ", "
|
||||
|
||||
sendmessage += taskinfo["statusName"] + "\x03\x033]\x03 "
|
||||
|
||||
# Put the title in there as well.
|
||||
sendmessage += taskinfo["title"].strip() + ": "
|
||||
|
||||
# And the link.
|
||||
sendmessage += "\x032" + taskinfo["uri"]
|
||||
|
||||
# Add the anchor back if it was present
|
||||
if anchor is not None:
|
||||
sendmessage += '#{}'.format(anchor)
|
||||
|
||||
sendmessage += '\x03'
|
||||
|
||||
# Send it off!
|
||||
self.send_notice(sendmessage)
|
||||
|
||||
# If someone wrote something like "Tblah", obviously that's not right.
|
||||
except ValueError:
|
||||
self.send_notice("\x034Error: " + task.strip() + "is an invalid task reference.\x03")
|
||||
return None
|
||||
|
||||
|
||||
def bot(self, message, msgtype):
|
||||
|
||||
if msgtype == "info":
|
||||
message = message.split(" :" + self.username + ": info")[1]
|
||||
|
||||
for item in message.split():
|
||||
if item.startswith("T") or item.startwith("D"):
|
||||
self.gettaskinfo(item.strip())
|
||||
|
||||
elif msgtype == "link":
|
||||
|
||||
for item in message.split(self.phab_host):
|
||||
if (item.split()[0].strip().startswith("T")) or \
|
||||
(item.split()[0].strip().startswith("D")):
|
||||
|
||||
self.gettaskinfo(item.split()[0].strip())
|
||||
|
||||
else:
|
||||
self.sendnotice("\x034Error: unknown command.\x03")
|
||||
return None
|
||||
|
||||
|
||||
def listen(self):
|
||||
|
||||
while True:
|
||||
ircmsg = self.conn.recv(512)
|
||||
|
||||
if len(ircmsg) == 0:
|
||||
# logger.warn is deprecated, use .warning.
|
||||
self.logger.warning("Connection lost, reconnecting!")
|
||||
self.connect()
|
||||
continue
|
||||
|
||||
ircmsg = ircmsg.decode("UTF-8").strip('\n\r')
|
||||
self.logger.debug(ircmsg)
|
||||
|
||||
if ircmsg.find("PING :") != -1:
|
||||
self.conn.send(bytes("PONG :pingis\n", "UTF-8"))
|
||||
|
||||
elif ircmsg.find(" :" + self.username + ": info") != -1:
|
||||
self.bot(ircmsg, "info")
|
||||
|
||||
elif ircmsg.find("{}".format(self.phab_host)) != -1:
|
||||
self.bot(ircmsg, "link")
|
@ -0,0 +1,66 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# S.D.G
|
||||
"""
|
||||
:mod:`lugito.connectors.launchpad`
|
||||
======================================
|
||||
|
||||
Define a launchpad connector class
|
||||
|
||||
.. currentmodule:: lugito.connectors.launchpad
|
||||
"""
|
||||
|
||||
# Imports
|
||||
import logging
|
||||
import phabricator
|
||||
from launchpadlib.launchpad import Launchpad
|
||||
|
||||
|
||||
class LPConnector(object):
|
||||
|
||||
def __init__(self, log_level=logging.DEBUG):
|
||||
|
||||
# Launchpad info
|
||||
# Read the configuration out of the .arcconfig file
|
||||
self.application = phabricator.ARCRC['launchpad']['application']
|
||||
self.staging = phabricator.ARCRC['launchpad']['staging']
|
||||
self.version = phabricator.ARCRC['launchpad']['version']
|
||||
self.supported_vers =\
|
||||
phabricator.ARCRC['launchpad']['supported_versions']
|
||||
|
||||
# Phabricator info
|
||||
self.phab = phabricator.Phabricator()
|
||||
self.phab_host = phabricator.ARCRC['config']['default'].replace(
|
||||
'api/', '')
|
||||
|
||||
self.logger = logging.getLogger('lugito.connector.LPConnector')
|
||||
|
||||
# Add log level
|
||||
ch = logging.StreamHandler()
|
||||
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
|
||||
self.logger.addHandler(ch)
|
||||
self.logger.setLevel(log_level)
|
||||
|
||||
|
||||
def connect(self):
|
||||
"""Connect"""
|
||||
|
||||
self.logger.info("Connecting to Launchpad")
|
||||
|
||||
|
||||
def send(self, objectstr, who, body, link):
|
||||
pass
|
||||
|
||||
|
||||
def listen(self):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
obj = LPConnector()
|
||||
|
@ -0,0 +1,257 @@
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
:mod:`lugito.lugito`
|
||||
======================================
|
||||
|
||||
Defines a class to interact with Phabricator
|
||||
|
||||
.. currentmodule:: lugito.lugito
|
||||
"""
|
||||
|
||||
import json
|
||||
import hmac
|
||||
import http
|
||||
import logging
|
||||
import phabricator
|
||||
from hashlib import sha256
|
||||
|
||||
PHAB_WEBHOOK_SIG = "X-Phabricator-Webhook-Signature"
|
||||
|
||||
COMMIT = "CMIT"
|
||||
DIFF_REV = "DREV"
|
||||
TASK = "TASK"
|
||||
|
||||
|
||||
class Lugito(object):
|
||||
|
||||
def __init__(self, log_level=logging.DEBUG):
|
||||
"""
|
||||
Initialise
|
||||
"""
|
||||
|
||||
self.phab = phabricator.Phabricator()
|
||||
self.HMAC = phabricator.ARCRC['HMAC']
|
||||
self.host = phabricator.ARCRC['config']['default']
|
||||
|
||||
self.logger = logging.getLogger('lugito.lugito')
|
||||
|
||||
# Add log level
|
||||
ch = logging.StreamHandler()
|
||||
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
|
||||
self.logger.addHandler(ch)
|
||||
self.logger.setLevel(log_level)
|
||||
|
||||
for key, val in self.HMAC.items():
|
||||
self.HMAC[key] = bytes(u'%s' % val, 'utf-8')
|
||||
|
||||
|
||||
def _transaction_search(self):
|
||||
self.transaction = self.phab.transaction.search(objectIdentifier=
|
||||
self.request_data["object"]["phid"])["data"]
|
||||
|
||||
def _request_data(self, request):
|
||||
self.request_data = json.loads(request.data)
|
||||
|
||||
|
||||
def validate_HMAC(self, hmac_key, request):
|
||||
"""
|
||||
Check a request originated from Phabricator. This method must be called
|
||||
first to validate a request is from Phabricator before any other method
|
||||
can be called
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
hmac_key: str
|
||||
The dictionary key corresponding to the HMAC token for the specifid webhook
|
||||
|
||||
request: flask request object
|
||||
The request object provided by the flask route decorator
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
result: boolean
|
||||
True if the request matches the specified HMAC key, False if not
|
||||
|
||||
"""
|
||||
|
||||
hash_ = hmac.new(self.HMAC[hmac_key], request.data, sha256)
|
||||
|
||||
# check if from phabricator
|
||||
if hash_.hexdigest() == request.headers[PHAB_WEBHOOK_SIG]:
|
||||
self._request_data(request)
|
||||
self._transaction_search()
|
||||
self.logger.info('received phid: %s' %\
|
||||
self.request_data["object"]["phid"])
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_object_type(self):
|
||||
"""
|
||||
Get object type from a request
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
request: flask request object
|
||||
The request object provided by the flask route decorator
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
object_type: str or None
|
||||
The object type from a request
|
||||
|
||||
"""
|
||||
object_type = self.request_data["object"]["type"]
|
||||
self.logger.debug('get_object_type: %s' % object_type)
|
||||
return object_type
|
||||
|
||||
|
||||
def get_author_fullname(self):
|
||||
"""
|
||||
Get author fullname from a request
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
request: flask request object
|
||||
The request object provided by the flask route decorator
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
author_name: str or None
|
||||
The fullname if the author object exists. If the object doesn't
|
||||
exist a blank string is returned.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
# Find the author too.
|
||||
userlookup = self.transaction[0]["authorPHID"]
|
||||
who = dict(self.phab.phid.query(
|
||||
phids=[userlookup]))[userlookup]["fullName"]
|
||||
|
||||
self.logger.debug('get_author_fullname: %s' % who)
|
||||
return who
|
||||
|
||||
# If the object exists, no worries, let's just return a good response.
|
||||
except http.client.HTTPException:
|
||||
self.logger.info('get_author_fullname is None')
|
||||
return None
|
||||
|
||||
|
||||
def get_object_string(self, key): #pragma: no cover
|
||||
|
||||
phid = self.request_data["object"]["phid"]
|
||||
return self.phab.phid.query(phids=[phid])[phid][key]
|
||||
|
||||
|
||||
def get_commit_message(self):
|
||||
"""
|
||||
Get the commit message
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
commit_message: str or None
|
||||
The commit message for the request if the author object exists.
|
||||
If the object doesn't exist a blank string is returned.
|
||||
|
||||
"""
|
||||
fullName = self.get_object_string(self.request_data, "fullName")
|
||||
name = self.get_object_string(self.request_data, "name")
|
||||
|
||||
commitmessage = fullName.replace(name + ": ", "")
|
||||
|
||||
self.logger.debug('get_commit_message: %s' % commitmessage)
|
||||
return commitmessage
|
||||
|
||||
|
||||
def is_new_object(self):
|
||||
"""
|
||||
Is the request from a newly created object
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
new_object: boolean
|
||||
True if a new searches else False
|
||||
|
||||
"""
|
||||
|
||||
newtask = None
|
||||
modified = None
|
||||
for data in self.transaction:
|
||||
if modified:
|
||||
if (data["dateCreated"] == data["dateModified"])\
|
||||
and (data["dateCreated"] == modified):
|
||||
modified = data["dateCreated"]
|
||||
newtask = True
|
||||
else:
|
||||
newtask = False
|
||||
break
|
||||
else:
|
||||
modified = data["dateCreated"]
|
||||
|
||||
return newtask
|
||||
|
||||
|
||||
def is_comment(self, period=10):
|
||||
"""
|
||||
Is the request from a new or edited comment object
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
request: flask request object
|
||||
The request object provided by the flask route decorator
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
is_new_comment: boolean
|
||||
True if the request is from a new comment
|
||||
|
||||
is_edited_comment: boolean
|
||||
True if the request is from an edtied comment
|
||||
|
||||
comment_id: string or None
|
||||
The id of the comment to use as an HTML anchor or None if no comment
|
||||
is referenced
|
||||
|
||||
"""
|
||||
|
||||
is_new_comment = False
|
||||
is_edited_comment = False
|
||||
comment_id = None
|
||||
for task in self.transaction:
|
||||
dataepoch = self.request_data["action"]["epoch"]
|
||||
datemodified = task["dateModified"]
|
||||
|
||||
# All comments within period seconds of the request are fair game.
|
||||
if (dataepoch - period) <= datemodified <= (dataepoch + period) and\
|
||||
task["comments"] != []:
|
||||
|
||||
comment_id = task["id"]
|
||||
|
||||
if datemodified != task["dateCreated"]:
|
||||
is_new_comment = False
|
||||
is_edited_comment = True
|
||||
else:
|
||||
is_new_comment = True
|
||||
is_edited_comment = False
|
||||
break
|
||||
|
||||
return (is_new_comment, is_edited_comment, comment_id)
|
@ -0,0 +1,142 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# S.D.G
|
||||
|
||||
"""
|
||||
|
||||
:mod:`lugito.webhooks`
|
||||
======================================
|
||||
|
||||
Lugito webhooks
|
||||
|
||||
.. currentmodule:: lugito.webhooks
|
||||
"""
|
||||
|
||||
# Imports
|
||||
import logging
|
||||
import threading
|
||||
from flask import Flask, request
|
||||
from lugito import Lugito
|
||||
from lugito.connectors.irc import IRCConnector
|
||||
|
||||
# Constants
|
||||
GLOBAL_LOG_LEVEL = logging.DEBUG
|
||||
|
||||
# Instantiate Lugito and connectors
|
||||
lugito = Lugito(GLOBAL_LOG_LEVEL)
|
||||
WEBSITE = lugito.host.replace('/api/', '')
|
||||
|
||||
irc_con = IRCConnector()
|
||||
|
||||
# Logging
|
||||
logger = logging.getLogger('lugito.webhooks')
|
||||
|
||||
# Add log level
|
||||
ch = logging.StreamHandler()
|
||||
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
|
||||
logger.addHandler(ch)
|
||||
logger.setLevel(GLOBAL_LOG_LEVEL)
|
||||
|
||||
# Flask
|
||||
app = Flask('lugito')
|
||||
|
||||
|
||||
@app.route("/irc", methods=["POST"])
|
||||
def _main():
|
||||
"""Main route"""
|
||||
|
||||
if lugito.validate_HMAC('irc', request):
|
||||
|
||||
author = lugito.get_author_fullname()
|
||||
|
||||
# Without the author we can't continue
|
||||
if author is None:
|
||||
return 'Ok'
|
||||
|
||||
logger.debug("Object exists, checking to see if it's a task, diff "\
|
||||
"or a commit.")
|
||||
|
||||
newtask = lugito.is_new_object()
|
||||
is_new_comment, is_edited, comment_id = lugito.is_comment()
|
||||
object_type = lugito.request_data["object"]["type"]
|
||||
|
||||
body = ""
|
||||
link = ""
|
||||
objectstr = lugito.get_object_string("fullName")
|
||||
|
||||
send_msg = True
|
||||
# Determine what event produced the webhook call
|
||||
if (object_type == "TASK") and newtask:
|
||||
logger.debug("Object is a new task.")
|
||||
body = "just created this task"
|
||||
link = lugito.get_object_string("uri")
|
||||
|
||||
elif (object_type == "TASK") and (not newtask):
|
||||
logger.debug("Object is NOT a new task.")
|
||||
|
||||
# Is it a new or edited comment
|
||||
if is_new_comment and (not is_edited):
|
||||
logger.debug("Object is a new comment.")
|
||||
body = "commented on the task"
|
||||
|
||||
elif (not is_new_comment) and is_edited:
|
||||
logger.debug("Object is an edited comment.")
|
||||
body = "edited a message on the task"
|
||||
|
||||
if is_new_comment or is_edited:
|
||||
link = lugito.get_object_string("uri")
|
||||
link += "#" + str(comment_id)
|
||||
logger.info(link)
|
||||
|
||||
else:
|
||||
logger.debug("The object has already been processed")
|
||||
send_msg = False
|
||||
|
||||
elif (object_type == "DREV") and newtask:
|
||||
logger.debug("Object is a new diff.")
|
||||
body = "just created this diff"
|
||||
link = lugito.get_object_string("uri")
|
||||
|
||||
elif (object_type == "DREV") and (not newtask):
|
||||
logger.debug("Object is NOT a new diff.")
|
||||
|
||||
# Is it a new or edited comment
|
||||
if is_new_comment and (not is_edited):
|
||||
logger.debug("Object is a new comment.")
|
||||
body = "commented on the diff"
|
||||
|
||||
elif (not is_new_comment) and is_edited:
|
||||
logger.debug("Object is an edited comment.")
|
||||
body = "edited a message on the diff"
|
||||
|
||||
if is_new_comment or is_edited:
|
||||
link = lugito.get_object_string("uri")
|
||||
link += "#" + str(comment_id)
|
||||
logger.info(link)
|
||||
|
||||
else:
|
||||
logger.debug("The object has already been processed")
|
||||
send_msg = False
|
||||
|
||||
elif object_type == "CMIT":
|
||||
logger.debug("Object is a commit.")
|
||||
body = "committed"
|
||||
link = WEBSITE + "/" + lugito.get_object_string("name")
|
||||
logger.info(link)
|
||||
|
||||
if send_msg:
|
||||
irc_con.send(objectstr, author, body, link)
|
||||
|
||||
return 'Ok'
|
||||
|
||||
|
||||
def run():
|
||||
irc_con.connect()
|
||||
t = threading.Thread(target=irc_con.listen)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
app.run(host="0.0.0.0", port=5000)
|
@ -0,0 +1,4 @@
|
||||
[pytest]
|
||||
python_files = tests.py test_*.py *_tests.py
|
||||
pytest_plugins = "pytest_cov", "pep8"
|
||||
addopts = --doctest-modules --cov-config=.coveragerc --cov=lugito --cov-report=term-missing
|
@ -1,3 +1,35 @@
|
||||
flask
|
||||
launchpadlib
|
||||
phabricator
|
||||
asn1crypto==0.24.0
|
||||
atomicwrites==1.2.1
|
||||
attrs==18.2.0
|
||||
cffi==1.11.5
|
||||
Click==7.0
|
||||
coverage==4.5.2
|
||||
cryptography==2.4.1
|
||||
distro==1.3.0
|
||||
entrypoints==0.2.3
|
||||
Flask==1.0.2
|
||||
httplib2==0.11.3
|
||||
idna==2.7
|
||||
itsdangerous==1.1.0
|
||||
jeepney==0.4
|
||||
Jinja2==2.10
|
||||
keyring==16.0.2
|
||||
launchpadlib==1.10.6
|
||||
lazr.restfulclient==0.14.0
|
||||
lazr.uri==1.0.3
|
||||
-e git+ssh://git@phab.lubuntu.me:2222/source/lugito.git@165c866a5d5a184b0b36552334fa11aba95b5fb8#egg=lugito
|
||||
MarkupSafe==1.1.0
|
||||
more-itertools==4.3.0
|
||||
oauthlib==2.1.0
|
||||
pbr==5.1.1
|
||||
phabricator==0.7.0
|
||||
pluggy==0.8.0
|
||||
py==1.7.0
|
||||
pycparser==2.19
|
||||
pytest==4.0.0
|
||||
pytest-cov==2.6.0
|
||||
SecretStorage==3.1.0
|
||||
six==1.11.0
|
||||
testresources==2.0.1
|
||||
wadllib==1.3.3
|
||||
Werkzeug==0.14.1
|
||||
|
@ -0,0 +1,12 @@
|
||||
pip==18.1
|
||||
bumpversion==0.5.3
|
||||
wheel==0.32.1
|
||||
watchdog==0.9.0
|
||||
flake8==3.5.0
|
||||
tox==3.5.2
|
||||
coverage==4.5.1
|
||||
Sphinx==1.8.1
|
||||
twine==1.12.1
|
||||
|
||||
pytest==3.8.2
|
||||
pytest-runner==4.2
|
@ -0,0 +1,38 @@
|
||||
[bumpversion]
|
||||
current_version = 0.1.0
|
||||
commit = True
|
||||
tag = True
|
||||
|
||||
[bumpversion:file:setup.py]
|
||||
search = version='{current_version}'
|
||||
replace = version='{new_version}'
|
||||
|
||||
[bumpversion:file:lugito/__init__.py]
|
||||
search = __version__ = '{current_version}'
|
||||
replace = __version__ = '{new_version}'
|
||||
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[flake8]
|
||||
exclude = docs
|
||||
|
||||
[aliases]
|
||||
# Define setup.py command aliases here
|
||||
test = pytest
|
||||
|
||||
[tool:pytest]
|
||||
collect_ignore = ['setup.py']
|
||||
|
||||
|
||||
# See the docstring in versioneer.py for instructions. Note that you must
|
||||
# re-run 'versioneer.py setup' after changing this section, and commit the
|
||||
# resulting files.
|
||||
|
||||
[versioneer]
|
||||
VCS = git
|
||||
style = pep440
|
||||
versionfile_source = lugito/_version.py
|
||||
versionfile_build = lugito/_version.py
|
||||
tag_prefix = ''
|
||||
parentdir_prefix = lugito-
|
@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""The setup script."""
|
||||
# import versioneer
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
with open('README.rst') as readme_file:
|
||||
readme = readme_file.read()
|
||||
|
||||
with open('HISTORY.rst') as history_file:
|
||||
history = history_file.read()
|
||||
|
||||
requirements = [
|
||||
'Click>=6.0',
|
||||
'Flask>=1.0.2',
|
||||
'httplib2==0.11.3', # BJ - Currently the latest version of httplib2 (0.12.0)
|
||||
# is not compatible with launchpadlib
|
||||
'launchpadlib>=1.10.6',
|
||||
'phabricator>=0.7.0',
|
||||
'versioneer>=0.18',
|
||||
]
|
||||
|
||||
setup_requirements = ['pytest-runner', ]
|
||||
|
||||
test_requirements = [
|
||||
'pytest',
|
||||
'pytest-cov',
|
||||
]
|
||||
|
||||
setup(
|
||||
author="",
|
||||
author_email='',
|
||||
classifiers=[
|
||||
'Development Status :: 2 - Pre-Alpha',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Natural Language :: English',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
],
|
||||
description="Python Boilerplate contains all the boilerplate "\
|
||||
"you need to create a Python package.",
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'lugito=lugito.cli:run',
|
||||
],
|
||||
},
|
||||
install_requires=requirements,
|
||||
license="BSD license",
|
||||
long_description=readme + '\n\n' + history,
|
||||
include_package_data=True,
|
||||
keywords='lugito',
|
||||
name='lugito',
|
||||
packages=find_packages(exclude=['*tests']),
|
||||
setup_requires=setup_requirements,
|
||||
test_suite='tests',
|
||||
tests_require=test_requirements,
|
||||
url='',
|
||||
version='0.1.0',
|
||||
zip_safe=False,
|
||||
# version=versioneer.get_version(),
|
||||
# cmdclass=versioneer.get_cmdclass(),
|
||||
)
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"hosts": {
|
||||
"http://127.0.0.1:9091/api/": {
|
||||
"token": "api-nojs2ip33hmp4zn6u6cf72w7d6yh"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"default": "http://127.0.0.1:9091/api/"
|
||||
},
|
||||
"HMAC": {
|
||||
"diffhook": "vglzi6t4gsumnilv27r27no7rs3vgs75",
|
||||
"commithook": "znkyfflbcia5gviqx5ybad7s6uyfywxi"
|
||||
},
|
||||
"irc": {
|
||||
"host": "irc.freenode.net",
|
||||
"port": "6697",
|
||||
"username": "someusername",
|
||||
"password": "somepassword",
|
||||
"channel": "#somechannel"
|
||||
},
|
||||
"launchpad": {
|
||||
"application": "lugito",
|
||||
"staging": "production",
|
||||
"version": "devel",
|
||||
"supported_versions": ["Cosmic", "Bionic", "Xenial", "Trusty"]
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
{
|
||||
"object": {
|
||||
"type": "DREV",
|
||||
"phid": "PHID-DREV-qxuxc6eankxb7rw7iusf"
|
||||
},
|
||||
"triggers": [
|
||||
{
|
||||
"phid": "PHID-HRUL-6shsdzbrq7d3kucru3nt"
|
||||
}
|
||||
],
|
||||
"action": {
|
||||
"test": false,
|
||||
"silent": false,
|
||||
"secure": false,
|
||||
"epoch": 1541502424
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"phid": "PHID-XACT-DREV-34sap7mpfbsucjw"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
{
|
||||
"object": {
|
||||
"type": "DREV",
|
||||
"phid": "PHID-DREV-rzehcd6ciu4d5lcqss32"
|
||||
},
|
||||
"triggers": [
|
||||
{
|
||||
"phid": "PHID-USER-5cmhaqtkggymhvbyqdcv"
|
||||
}
|
||||
],
|
||||
"action": {
|
||||
"test": true,
|
||||
"silent": false,
|
||||
"secure": false,
|
||||
"epoch": 1542157769
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"phid": "PHID-XACT-DREV-ysczigtbjgxwghd"
|
||||
},
|
||||
{
|
||||
"phid": "PHID-XACT-DREV-5yesmolevks6nk2"
|
||||
},
|
||||
{
|
||||
"phid": "PHID-XACT-DREV-wbzhl2ctk7klx33"
|
||||
},
|
||||
{
|
||||
"phid": "PHID-XACT-DREV-kjxh5l47uj77yfw"
|
||||
},
|
||||
{
|
||||
"phid": "PHID-XACT-DREV-vgo26mo4fpmwp2n"
|
||||
},
|
||||
{
|
||||
"phid": "PHID-XACT-DREV-5muazco3m4qjxqo"
|
||||
},
|
||||
{
|
||||
"phid": "PHID-XACT-DREV-q4axjuuaarmc43x"
|
||||
},
|
||||
{
|
||||
"phid": "PHID-XACT-DREV-6ayyl5ky5r5vqib"
|
||||
},
|
||||
{
|
||||
"phid": "PHID-XACT-DREV-przqed7mheiygzq"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
{
|
||||
"object": {
|
||||
"type": "DREV",
|
||||
"phid": "PHID-DREV-z5l6ecfaugf6abnwvjeg"
|
||||
},
|
||||
"triggers": [
|
||||
{
|
||||
"phid": "PHID-HRUL-6shsdzbrq7d3kucru3nt"
|
||||
}
|
||||
],
|
||||
"action": {
|
||||
"test": false,
|
||||
"silent": false,
|
||||
"secure": false,
|
||||
"epoch": 1542019737
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"phid": "PHID-XACT-DREV-aklgm5tv34ajmcp"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
[
|
||||
{
|
||||
"id": 58,
|
||||
"phid": "PHID-XACT-DREV-uzd7ragh2zwabgj",
|
||||
"type": "comment",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-qxuxc6eankxb7rw7iusf",
|
||||
"dateCreated": 1541589352,
|
||||
"dateModified": 1541589352,
|
||||
"comments": [
|
||||
{
|
||||
"id": 20,
|
||||
"phid": "PHID-XCMT-hkoz5viifwgvvgzzvwee",
|
||||
"version": 1,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1541589352,
|
||||
"dateModified": 1541589352,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": ",mn,mn,mn"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"phid": "PHID-XACT-DREV-s4ttw2voosilkke",
|
||||
"type": "comment",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-qxuxc6eankxb7rw7iusf",
|
||||
"dateCreated": 1541589290,
|
||||
"dateModified": 1541589290,
|
||||
"comments": [
|
||||
{
|
||||
"id": 19,
|
||||
"phid": "PHID-XCMT-tedacg7scqz67lfxtyoc",
|
||||
"version": 1,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1541589290,
|
||||
"dateModified": 1541589290,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "lkjhkjhkjh"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fields": {}
|
||||
}
|
||||
]
|
@ -0,0 +1,243 @@
|
||||
[
|
||||
{
|
||||
"id": 158,
|
||||
"phid": "PHID-XACT-DREV-ysczigtbjgxwghd",
|
||||
"type": "comment",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-rzehcd6ciu4d5lcqss32",
|
||||
"dateCreated": 1542050802,
|
||||
"dateModified": 1542157734,
|
||||
"comments": [
|
||||
{
|
||||
"id": 56,
|
||||
"phid": "PHID-XCMT-2y6tf2nhf4gbtn7yyiid",
|
||||
"version": 3,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542157734,
|
||||
"dateModified": 1542157734,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "edit comment 4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"phid": "PHID-XCMT-cj7zihk3sjghqm4tnqcm",
|
||||
"version": 2,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542050934,
|
||||
"dateModified": 1542050934,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "editing another comment"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"phid": "PHID-XCMT-ipf5ypa52hjiddsnprjs",
|
||||
"version": 1,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542050802,
|
||||
"dateModified": 1542050802,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "a new comment"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 157,
|
||||
"phid": "PHID-XACT-DREV-5yesmolevks6nk2",
|
||||
"type": "comment",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-rzehcd6ciu4d5lcqss32",
|
||||
"dateCreated": 1542050466,
|
||||
"dateModified": 1542157769,
|
||||
"comments": [
|
||||
{
|
||||
"id": 55,
|
||||
"phid": "PHID-XCMT-7ugmycz5xjf4txw4vxeh",
|
||||
"version": 5,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542157769,
|
||||
"dateModified": 1542157769,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": " edit comment 3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"phid": "PHID-XCMT-sjgxizrji2oova2a3rsr",
|
||||
"version": 4,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542157657,
|
||||
"dateModified": 1542157657,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "edit comment 3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"phid": "PHID-XCMT-5nbt6zmh3iteqozndjaz",
|
||||
"version": 3,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542051079,
|
||||
"dateModified": 1542051079,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "another edited comment"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"phid": "PHID-XCMT-dsiy2lhvwadrp2xl2m3y",
|
||||
"version": 2,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542050506,
|
||||
"dateModified": 1542050506,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "an edited new comment"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"phid": "PHID-XCMT-6wflirrwqccfkaqppwn5",
|
||||
"version": 1,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542050466,
|
||||
"dateModified": 1542050466,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "a new comment"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 156,
|
||||
"phid": "PHID-XACT-DREV-wbzhl2ctk7klx33",
|
||||
"type": "comment",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-rzehcd6ciu4d5lcqss32",
|
||||
"dateCreated": 1542050158,
|
||||
"dateModified": 1542050414,
|
||||
"comments": [
|
||||
{
|
||||
"id": 46,
|
||||
"phid": "PHID-XCMT-qzwqn672ti54rvtuz64o",
|
||||
"version": 3,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542050414,
|
||||
"dateModified": 1542050414,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "an edited comment 2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"phid": "PHID-XCMT-dfxoavdijxvdiacg3tuy",
|
||||
"version": 2,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542050213,
|
||||
"dateModified": 1542050213,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "an edited comment"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"phid": "PHID-XCMT-5lgf6nbudf4tueyggtzm",
|
||||
"version": 1,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542050158,
|
||||
"dateModified": 1542050158,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "a comment"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 155,
|
||||
"phid": "PHID-XACT-DREV-kjxh5l47uj77yfw",
|
||||
"type": null,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-rzehcd6ciu4d5lcqss32",
|
||||
"dateCreated": 1542022249,
|
||||
"dateModified": 1542022249,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 154,
|
||||
"phid": "PHID-XACT-DREV-vgo26mo4fpmwp2n",
|
||||
"type": null,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-rzehcd6ciu4d5lcqss32",
|
||||
"dateCreated": 1542022249,
|
||||
"dateModified": 1542022249,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 153,
|
||||
"phid": "PHID-XACT-DREV-5muazco3m4qjxqo",
|
||||
"type": null,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-rzehcd6ciu4d5lcqss32",
|
||||
"dateCreated": 1542022249,
|
||||
"dateModified": 1542022249,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 152,
|
||||
"phid": "PHID-XACT-DREV-q4axjuuaarmc43x",
|
||||
"type": "title",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-rzehcd6ciu4d5lcqss32",
|
||||
"dateCreated": 1542022249,
|
||||
"dateModified": 1542022249,
|
||||
"comments": [],
|
||||
"fields": {
|
||||
"old": "",
|
||||
"new": "pope-hamas"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 151,
|
||||
"phid": "PHID-XACT-DREV-6ayyl5ky5r5vqib",
|
||||
"type": "update",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-rzehcd6ciu4d5lcqss32",
|
||||
"dateCreated": 1542022249,
|
||||
"dateModified": 1542022249,
|
||||
"comments": [],
|
||||
"fields": {
|
||||
"old": null,
|
||||
"new": "PHID-DIFF-3u3lmekxjyeoq6xaxfd4",
|
||||
"commitPHIDs": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 150,
|
||||
"phid": "PHID-XACT-DREV-przqed7mheiygzq",
|
||||
"type": "create",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-rzehcd6ciu4d5lcqss32",
|
||||
"dateCreated": 1542022249,
|
||||
"dateModified": 1542022249,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
}
|
||||
]
|
@ -0,0 +1,171 @@
|
||||
[
|
||||
{
|
||||
"id": 136,
|
||||
"phid": "PHID-XACT-DREV-wzobfd22ohrg6m4",
|
||||
"type": "comment",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-z5l6ecfaugf6abnwvjeg",
|
||||
"dateCreated": 1542020438,
|
||||
"dateModified": 1542020438,
|
||||
"comments": [
|
||||
{
|
||||
"id": 35,
|
||||
"phid": "PHID-XCMT-x4n2osxvqizmnhymiq5d",
|
||||
"version": 1,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542020438,
|
||||
"dateModified": 1542020438,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "new comment"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 135,
|
||||
"phid": "PHID-XACT-DREV-l6axinwdgij5qee",
|
||||
"type": "comment",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-z5l6ecfaugf6abnwvjeg",
|
||||
"dateCreated": 1542019934,
|
||||
"dateModified": 1542019934,
|
||||
"comments": [
|
||||
{
|
||||
"id": 34,
|
||||
"phid": "PHID-XCMT-57ax765vxfwbsumfn2mb",
|
||||
"version": 1,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542019934,
|
||||
"dateModified": 1542019934,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "some other comment"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 134,
|
||||
"phid": "PHID-XACT-DREV-dguhwsggckgiynl",
|
||||
"type": "comment",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-z5l6ecfaugf6abnwvjeg",
|
||||
"dateCreated": 1542019837,
|
||||
"dateModified": 1542019837,
|
||||
"comments": [
|
||||
{
|
||||
"id": 33,
|
||||
"phid": "PHID-XCMT-yoyikx5jpuixc5baxiu4",
|
||||
"version": 1,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542019837,
|
||||
"dateModified": 1542019837,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "some other comment"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 133,
|
||||
"phid": "PHID-XACT-DREV-aklgm5tv34ajmcp",
|
||||
"type": "comment",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-z5l6ecfaugf6abnwvjeg",
|
||||
"dateCreated": 1542019737,
|
||||
"dateModified": 1542019737,
|
||||
"comments": [
|
||||
{
|
||||
"id": 32,
|
||||
"phid": "PHID-XCMT-b7k3k2tzyfrduaymeauo",
|
||||
"version": 1,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"dateCreated": 1542019737,
|
||||
"dateModified": 1542019737,
|
||||
"removed": false,
|
||||
"content": {
|
||||
"raw": "a comment here"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 132,
|
||||
"phid": "PHID-XACT-DREV-jxajlff675zoqik",
|
||||
"type": null,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-z5l6ecfaugf6abnwvjeg",
|
||||
"dateCreated": 1542014500,
|
||||
"dateModified": 1542014500,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 131,
|
||||
"phid": "PHID-XACT-DREV-x6qr3lgaxukqawh",
|
||||
"type": null,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-z5l6ecfaugf6abnwvjeg",
|
||||
"dateCreated": 1542014500,
|
||||
"dateModified": 1542014500,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 130,
|
||||
"phid": "PHID-XACT-DREV-6eulucm7uftvapb",
|
||||
"type": null,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-z5l6ecfaugf6abnwvjeg",
|
||||
"dateCreated": 1542014500,
|
||||
"dateModified": 1542014500,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 129,
|
||||
"phid": "PHID-XACT-DREV-qq26utftpfwjtjo",
|
||||
"type": "title",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-z5l6ecfaugf6abnwvjeg",
|
||||
"dateCreated": 1542014500,
|
||||
"dateModified": 1542014500,
|
||||
"comments": [],
|
||||
"fields": {
|
||||
"old": "",
|
||||
"new": "create diff again"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 128,
|
||||
"phid": "PHID-XACT-DREV-aawvabsf6kfhksh",
|
||||
"type": "update",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-z5l6ecfaugf6abnwvjeg",
|
||||
"dateCreated": 1542014500,
|
||||
"dateModified": 1542014500,
|
||||
"comments": [],
|
||||
"fields": {
|
||||
"old": null,
|
||||
"new": "PHID-DIFF-bsvtdhdz63wq65wjttqg",
|
||||
"commitPHIDs": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 127,
|
||||
"phid": "PHID-XACT-DREV-j2a6dzebwmdxmvh",
|
||||
"type": "create",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-z5l6ecfaugf6abnwvjeg",
|
||||
"dateCreated": 1542014500,
|
||||
"dateModified": 1542014500,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
}
|
||||
]
|
@ -0,0 +1,97 @@
|
||||
[
|
||||
{
|
||||
"id": 101,
|
||||
"phid": "PHID-XACT-DREV-gfcfrpinnv4spcp",
|
||||
"type": null,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-nwspiigk5kbatjniv5jk",
|
||||
"dateCreated": 1541930934,
|
||||
"dateModified": 1541930934,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 100,
|
||||
"phid": "PHID-XACT-DREV-mfbe2f53t2gxwyl",
|
||||
"type": null,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-nwspiigk5kbatjniv5jk",
|
||||
"dateCreated": 1541930934,
|
||||
"dateModified": 1541930934,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 99,
|
||||
"phid": "PHID-XACT-DREV-7vy7l2d5hlgxaky",
|
||||
"type": null,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-nwspiigk5kbatjniv5jk",
|
||||
"dateCreated": 1541930934,
|
||||
"dateModified": 1541930934,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 98,
|
||||
"phid": "PHID-XACT-DREV-jhjbnikjbbwwfbw",
|
||||
"type": null,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-nwspiigk5kbatjniv5jk",
|
||||
"dateCreated": 1541930934,
|
||||
"dateModified": 1541930934,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 97,
|
||||
"phid": "PHID-XACT-DREV-l3ixndqztyuoocw",
|
||||
"type": null,
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-nwspiigk5kbatjniv5jk",
|
||||
"dateCreated": 1541930934,
|
||||
"dateModified": 1541930934,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
},
|
||||
{
|
||||
"id": 96,
|
||||
"phid": "PHID-XACT-DREV-v32dun652f2hcbc",
|
||||
"type": "title",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-nwspiigk5kbatjniv5jk",
|
||||
"dateCreated": 1541930934,
|
||||
"dateModified": 1541930934,
|
||||
"comments": [],
|
||||
"fields": {
|
||||
"old": "",
|
||||
"new": "a new diff erv"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 95,
|
||||
"phid": "PHID-XACT-DREV-f2ijnff2fzpafef",
|
||||
"type": "update",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-nwspiigk5kbatjniv5jk",
|
||||
"dateCreated": 1541930934,
|
||||
"dateModified": 1541930934,
|
||||
"comments": [],
|
||||
"fields": {
|
||||
"old": null,
|
||||
"new": "PHID-DIFF-7s75ei7xmgmgselnszd5",
|
||||
"commitPHIDs": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 94,
|
||||
"phid": "PHID-XACT-DREV-spddbfytwd6ziqi",
|
||||
"type": "create",
|
||||
"authorPHID": "PHID-USER-5cmhaqtkggymhvbyqdcv",
|
||||
"objectPHID": "PHID-DREV-nwspiigk5kbatjniv5jk",
|
||||
"dateCreated": 1541930934,
|
||||
"dateModified": 1541930934,
|
||||
"comments": [],
|
||||
"fields": {}
|
||||
}
|
||||
]
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"object":
|
||||
{
|
||||
"type": "DREV",
|
||||
"phid": "PHID-DREV-qxuxc6eankxb7rw7iusf"
|
||||
},
|
||||
"triggers": [{"phid": "PHID-HRUL-6shsdzbrq7d3kucru3nt"}],
|
||||
"action": {
|
||||
"test": "False",
|
||||
"silent": "False",
|
||||
"secure": "False",
|
||||
"epoch": 1541503686
|
||||
},
|
||||
"transactions": [{"phid": "PHID-XACT-DREV-2swyqdhax7hkc5r"}]
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# S.D.G
|
||||
|
||||
"""
|
||||
Test IRC connector
|
||||
"""
|
||||
|
||||
# Imports
|
||||
import os
|
||||
import json
|
||||
import phabricator
|
||||
from lugito import IRCConnector
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# Setup ###############################################################
|
||||
|
||||
TEST_DIR = os.path.dirname(__file__)
|
||||
|
||||
# Force phabricator to use the ./tests/.arcconfig file
|
||||
TEST_CONFIG = os.path.join(TEST_DIR, '.arcconfig')
|
||||
|
||||
with open(TEST_CONFIG, 'r') as f:
|
||||
phabricator.ARCRC = json.load(f)
|
||||
|
||||
# Tests ###############################################################
|
||||
|
||||
def test_init():
|
||||
"""Test initialise irc connector"""
|
||||
|
||||
obj = IRCConnector()
|
||||
|
||||
assert('irc.freenode.net' == obj.host)
|
||||
assert(6697 == obj.port)
|
||||
assert('someusername' == obj.username)
|
||||
assert('somepassword' == obj.password)
|
||||
assert('#somechannel' == obj.channel)
|
||||
assert('http://127.0.0.1:9091/' == obj.phab_host)
|
||||
|
||||
|
||||
def test_connect():
|
||||
"""Test initial connection"""
|
||||
|
||||
obj = IRCConnector()
|
||||
|
||||
obj._socket_conn = MagicMock()
|
||||
|
||||
# obj.conn.recv = MagicMock(side_effect=[
|
||||
|
@ -0,0 +1,27 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# S.D.G
|
||||
|
||||
"""
|
||||
Test launchpad connector
|
||||
"""
|
||||
|
||||
# Imports
|
||||
import json
|
||||
import phabricator
|
||||
from lugito.connectors.launchpad import LPConnector
|
||||
|
||||
# Setup ###############################################################
|
||||
|
||||
TEST_DIR = os.path.dirname(__file__)
|
||||
|
||||
# Force phabricator to use the ./tests/.arcconfig file
|
||||
TEST_CONFIG = os.path.join(TEST_DIR, '.arcconfig')
|
||||
|
||||
with open(TEST_CONFIG, 'r') as f:
|
||||
phabricator.ARCRC = json.load(f)
|
||||
|
||||
# Tests ###############################################################
|
||||
|
||||
|
||||
def test_init()
|
@ -0,0 +1,208 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# S.D.G
|
||||
|
||||
"""
|
||||
lugito tests
|
||||
"""
|
||||
|
||||
# Imports
|
||||
import os
|
||||
import pytest
|
||||
import phabricator
|
||||
import json
|
||||
import http
|
||||
from lugito import Lugito
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# Setup ###############################################################
|
||||
|
||||
TEST_DIR = os.path.dirname(__file__)
|
||||
|
||||
# Force phabricator to use the ./tests/.arcconfig file
|
||||
TEST_CONFIG = os.path.join(TEST_DIR, '.arcconfig')
|
||||
|
||||
with open(TEST_CONFIG, 'r') as f:
|
||||
phabricator.ARCRC = json.load(f)
|
||||
|
||||
# Pre-prepared request
|
||||
FAKE_REQUEST = os.path.join(TEST_DIR, 'request.json')
|
||||
|
||||
FAKE_REQ_DATA = os.path.join(TEST_DIR, 'fake_req_data.json')
|
||||
FAKE_TRANSACTION = os.path.join(TEST_DIR, 'fake_transaction.json')
|
||||
FAKE_TRANSACTION_NEW_OBJECT = os.path.join(
|
||||
TEST_DIR, 'fake_transaction_new_object.json')
|
||||
|
||||
FAKE_REQ_NEW_COMMENT = os.path.join(TEST_DIR, 'fake_req_data_new_comment.json')
|
||||
FAKE_NEW_COMMENT = os.path.join(TEST_DIR, 'fake_transaction_new_comment.json')
|
||||
|
||||
FAKE_REQ_EDITED_COMMENT = os.path.join(TEST_DIR,
|
||||
'fake_req_data_edit_comment.json')
|
||||
FAKE_EDITED_COMMENT = os.path.join(TEST_DIR,
|
||||
'fake_transaction_edit_comment.json')
|
||||
|
||||
# Tests ###############################################################
|
||||
|
||||
def test_init():
|
||||
"""Test initialise lugito"""
|
||||
|
||||
obj = Lugito()
|
||||
|
||||
assert('http://127.0.0.1:9091/api/' in obj.phab.host)
|
||||
assert('api-nojs2ip33hmp4zn6u6cf72w7d6yh' in obj.phab.token)
|
||||
assert(obj.HMAC['diffhook'] == bytes(u'vglzi6t4gsumnilv27r27no7rs3vgs75',
|
||||
'utf-8'))
|
||||
assert(obj.HMAC['commithook'] == bytes(u'znkyfflbcia5gviqx5ybad7s6uyfywxi',
|
||||
'utf-8'))
|
||||
|
||||
|
||||
def test_validate_HMAC():
|
||||
"""Test validating HMAC"""
|
||||
|
||||
obj = Lugito()
|
||||
|
||||
request_mock = MagicMock()
|
||||
|
||||
with open(FAKE_REQ_DATA, 'r') as f:
|
||||
request_mock.data = f.read().encode()
|
||||
|
||||
request_mock.headers = {
|
||||
"X-Phabricator-Webhook-Signature":
|
||||
"a8f636f03ed4464ddb398ea873ffab409d941f87396f28fa9d22bb58cfbedc9f"
|
||||
}
|
||||
|
||||
assert(obj.validate_HMAC('diffhook', request_mock))
|
||||
|
||||
|
||||
def test_invalid_HMAC():
|
||||
"""Test validating HMAC"""
|
||||
|
||||
obj = Lugito()
|
||||
|
||||
request_mock = MagicMock()
|
||||
|
||||
with open(FAKE_REQ_DATA, 'r') as f:
|
||||
request_mock.data = f.read().encode()
|
||||
|
||||
request_mock.headers = {
|
||||
"X-Phabricator-Webhook-Signature":
|
||||
"a8f6364464ddb398ea873ffab409d941f87396f28fa9d22bb58cfbedc9f"
|
||||
}
|
||||
|
||||
assert(not obj.validate_HMAC('diffhook', request_mock))
|
||||
|
||||
|
||||
def test_author_fullname():
|
||||
"""Test get the author name"""
|
||||
|
||||
obj = Lugito()
|
||||
|
||||
with open(FAKE_REQ_DATA, 'r') as f:
|
||||
obj.request_data = json.load(f)
|
||||
|
||||
with open(FAKE_TRANSACTION, 'r') as f:
|
||||
obj.transaction = json.load(f)
|
||||
|
||||
obj.phab = MagicMock()
|
||||
obj.phab.phid.query = MagicMock(return_value={
|
||||
'PHID-USER-5cmhaqtkggymhvbyqdcv':{
|
||||
'fullName': 'AuthorName',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
author_name = obj.get_author_fullname()
|
||||
assert(author_name == 'AuthorName')
|
||||
|
||||
|
||||
def test_author_fullname_error():
|
||||
"""Test unable to get the author name"""
|
||||
|
||||
obj = Lugito()
|
||||
|
||||
with open(FAKE_REQ_DATA, 'r') as f:
|
||||
obj.request_data = json.load(f)
|
||||
|
||||
with open(FAKE_TRANSACTION, 'r') as f:
|
||||
obj.transaction = json.load(f)
|
||||
|
||||
obj.phab = MagicMock()
|
||||
obj.phab.phid.query = MagicMock(side_effect=http.client.HTTPException)
|
||||
|
||||
author_name = obj.get_author_fullname()
|
||||
assert(author_name is None)
|
||||
|
||||
|
||||
def test_get_object_type():
|
||||
"""Test get object type"""
|
||||
|
||||
obj = Lugito()
|
||||
|
||||
with open(FAKE_REQ_DATA, 'r') as f:
|
||||
obj.request_data = json.load(f)
|
||||
|
||||
obj._transaction_search()
|
||||
|
||||
assert(obj.get_object_type() == 'DREV')
|
||||
|
||||
def test_is_new_object_false():
|
||||
"""Test is new task - false"""
|
||||
|
||||
obj = Lugito()
|
||||
|
||||
with open(FAKE_REQ_DATA, 'r') as f:
|
||||
obj.request_data = json.load(f)
|
||||
|
||||
with open(FAKE_TRANSACTION, 'r') as f:
|
||||
obj.transaction = json.load(f)
|
||||
|
||||
assert (not obj.is_new_object())
|
||||
|
||||
def test_is_new_object_true():
|
||||
"""Test is new task - true"""
|
||||
|
||||
obj = Lugito()
|
||||
|
||||
with open(FAKE_REQ_DATA, 'r') as f:
|
||||
obj.request_data = json.load(f)
|
||||
|
||||
with open(FAKE_TRANSACTION_NEW_OBJECT, 'r') as f:
|
||||
obj.transaction = json.load(f)
|
||||
|
||||
assert (obj.is_new_object())
|
||||
|
||||
|
||||
def test_is_new_comment():
|
||||
"""Test checking for new - using transaction search"""
|
||||
|
||||
obj = Lugito()
|
||||
|
||||
with open(FAKE_REQ_NEW_COMMENT, 'r') as f:
|
||||
obj.request_data = json.load(f)
|
||||
|
||||
with open(FAKE_NEW_COMMENT, 'r') as f:
|
||||
obj.transaction = json.load(f)
|
||||
|
||||
new_comment, edited, _id = obj.is_comment()
|
||||
|
||||
assert(new_comment)
|
||||
assert(not edited)
|
||||
assert(_id == 133)
|
||||
|
||||
|
||||
def test_is_edited_comment():
|
||||
"""Test checking for edited comment"""
|
||||
|
||||
obj = Lugito()
|
||||
|
||||
with open(FAKE_REQ_EDITED_COMMENT, 'r') as f:
|
||||
obj.request_data = json.load(f)
|
||||
|
||||
with open(FAKE_EDITED_COMMENT, 'r') as f:
|
||||
obj.transaction = json.load(f)
|
||||
|
||||
new_comment, edited, _id = obj.is_comment()
|
||||
|
||||
assert(not new_comment)
|
||||
assert(edited)
|
||||
assert(_id == 157)
|
@ -0,0 +1,28 @@
|
||||
[tox]
|
||||
envlist = py27, py34, py35, py36, flake8
|
||||
|
||||
[travis]
|
||||
python =
|
||||
3.6: py36
|
||||
3.5: py35
|
||||
3.4: py34
|
||||
2.7: py27
|
||||
|
||||
[testenv:flake8]
|
||||
basepython = python
|
||||
deps = flake8
|
||||
commands = flake8 lugito
|
||||
|
||||
[testenv]
|
||||
setenv =
|
||||
PYTHONPATH = {toxinidir}
|
||||
deps =
|
||||
-r{toxinidir}/requirements_dev.txt
|
||||
; If you want to make tox run the tests with the same versions, create a
|
||||
; requirements.txt with the pinned versions and uncomment the following line:
|
||||
; -r{toxinidir}/requirements.txt
|
||||
commands =
|
||||
pip install -U pip
|
||||
py.test --basetemp={envtmpdir}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue