├── .editorconfig ├── .gitignore ├── .python-version ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── Readme.md ├── assets ├── preview.gif └── progress.gif ├── docs ├── Makefile ├── authors.rst ├── conf.py ├── contributing.rst ├── history.rst ├── index.rst ├── installation.rst ├── make.bat ├── readme.rst └── usage.rst ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── bots_test.py ├── cli_test.py ├── fixtures │ └── vcr_cassettes │ │ ├── bdd_bots.yaml │ │ ├── bdd_cancel_integration.yaml │ │ ├── bdd_integrations.yaml │ │ ├── bdd_new_integration.yaml │ │ ├── bot.yaml │ │ ├── bots.yaml │ │ ├── integration.yaml │ │ ├── integration_cancel.yaml │ │ ├── integration_new.yaml │ │ ├── integration_running.yaml │ │ └── integrations.yaml ├── integrations_test.py ├── settings_test.py └── xcode_server_test.py ├── tox.ini ├── xserverpy.py ├── xserverpy.sublime-project └── xserverpy ├── __init__.py ├── display ├── __init__.py ├── bots_printer.py └── integrations_printer.py ├── lib ├── __init__.py ├── base_service.py ├── bot.py ├── bots.py ├── cli.py ├── integration.py ├── integration_watcher.py ├── integrations.py ├── user.py └── xcode_server.py ├── utils ├── __init__.py ├── config.py ├── porgress.py ├── settings.py └── version.py └── xserverpy.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | htmlcov 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | # Complexity 39 | output/*.html 40 | output/*/index.html 41 | 42 | # Sphinx 43 | docs/_build 44 | 45 | # Created by https://www.gitignore.io 46 | 47 | ### Python ### 48 | # Byte-compiled / optimized / DLL files 49 | __pycache__/ 50 | *.py[cod] 51 | 52 | # C extensions 53 | *.so 54 | 55 | # Distribution / packaging 56 | .Python 57 | env/ 58 | build/ 59 | develop-eggs/ 60 | dist/ 61 | downloads/ 62 | eggs/ 63 | .eggs/ 64 | lib/ 65 | lib64/ 66 | parts/ 67 | sdist/ 68 | var/ 69 | *.egg-info/ 70 | .installed.cfg 71 | *.egg 72 | 73 | # PyInstaller 74 | # Usually these files are written by a python script from a template 75 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 76 | *.manifest 77 | *.spec 78 | 79 | # Installer logs 80 | pip-log.txt 81 | pip-delete-this-directory.txt 82 | 83 | # Unit test / coverage reports 84 | htmlcov/ 85 | .tox/ 86 | .coverage 87 | .coverage.* 88 | .cache 89 | nosetests.xml 90 | coverage.xml 91 | *,cover 92 | 93 | # Translations 94 | *.mo 95 | *.pot 96 | 97 | # Django stuff: 98 | *.log 99 | 100 | # Sphinx documentation 101 | docs/_build/ 102 | 103 | # PyBuilder 104 | target/ 105 | 106 | 107 | ### SublimeText ### 108 | # cache files for sublime text 109 | *.tmlanguage.cache 110 | *.tmPreferences.cache 111 | *.stTheme.cache 112 | 113 | # workspace files are user-specific 114 | *.sublime-workspace 115 | 116 | # project files should be checked into the repository, unless a significant 117 | # proportion of contributors will probably not be using SublimeText 118 | # *.sublime-project 119 | 120 | # sftp configuration file 121 | sftp-config.json 122 | /out 123 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 2.7.9 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.org 2 | 3 | language: python 4 | 5 | python: 6 | - "2.7" 7 | - "2.7.9" 8 | 9 | env: 10 | - PIP_USE_MIRRORS=true 11 | 12 | install: 13 | - pip install coveralls 14 | - pip install -r requirements.txt 15 | - python setup.py build 16 | 17 | script: make coverage-no-open 18 | 19 | after_success: 20 | coveralls 21 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Omar Abdelhafith 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/oarrabi/xserverpy/issues. 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Fix Bugs 25 | ~~~~~~~~ 26 | 27 | Look through the GitHub issues for bugs. Anything tagged with "bug" 28 | is open to whoever wants to implement it. 29 | 30 | Implement Features 31 | ~~~~~~~~~~~~~~~~~~ 32 | 33 | Look through the GitHub issues for features. Anything tagged with "feature" 34 | is open to whoever wants to implement it. 35 | 36 | Write Documentation 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | xserverpy could always use more documentation, whether as part of the 40 | official xserverpy docs, in docstrings, or even on the web in blog posts, 41 | articles, and such. 42 | 43 | Submit Feedback 44 | ~~~~~~~~~~~~~~~ 45 | 46 | The best way to send feedback is to file an issue at https://github.com/oarrabi/xserverpy/issues. 47 | 48 | If you are proposing a feature: 49 | 50 | * Explain in detail how it would work. 51 | * Keep the scope as narrow as possible, to make it easier to implement. 52 | * Remember that this is a volunteer-driven project, and that contributions 53 | are welcome :) 54 | 55 | Get Started! 56 | ------------ 57 | 58 | Ready to contribute? Here's how to set up `xserverpy` for local development. 59 | 60 | 1. Fork the `xserverpy` repo on GitHub. 61 | 2. Clone your fork locally:: 62 | 63 | $ git clone git@github.com:your_name_here/xserverpy.git 64 | 65 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 66 | 67 | $ mkvirtualenv xserverpy 68 | $ cd xserverpy/ 69 | $ python setup.py develop 70 | 71 | 4. Create a branch for local development:: 72 | 73 | $ git checkout -b name-of-your-bugfix-or-feature 74 | 75 | Now you can make your changes locally. 76 | 77 | 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: 78 | 79 | $ flake8 xserverpy tests 80 | $ python setup.py test 81 | $ tox 82 | 83 | To get flake8 and tox, just pip install them into your virtualenv. 84 | 85 | 6. Commit your changes and push your branch to GitHub:: 86 | 87 | $ git add . 88 | $ git commit -m "Your detailed description of your changes." 89 | $ git push origin name-of-your-bugfix-or-feature 90 | 91 | 7. Submit a pull request through the GitHub website. 92 | 93 | Pull Request Guidelines 94 | ----------------------- 95 | 96 | Before you submit a pull request, check that it meets these guidelines: 97 | 98 | 1. The pull request should include tests. 99 | 2. If the pull request adds functionality, the docs should be updated. Put 100 | your new functionality into a function with a docstring, and add the 101 | feature to the list in README.rst. 102 | 3. The pull request should work for Python 2.6, 2.7, 3.3, and 3.4, and for PyPy. Check 103 | https://travis-ci.org/oarrabi/xserverpy/pull_requests 104 | and make sure that the tests pass for all supported Python versions. 105 | 106 | Tips 107 | ---- 108 | 109 | To run a subset of tests:: 110 | 111 | $ python -m unittest tests.test_xserverpy 112 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | .. :changelog: 2 | 3 | History 4 | ------- 5 | 6 | 0.1.0 (2015-01-11) 7 | --------------------- 8 | 9 | * First release on PyPI. 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Omar Abdelhafith 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | * Neither the name of xserverpy nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include Readme.md 6 | 7 | recursive-include tests * 8 | 9 | 10 | recursive-include docs *.rst conf.py Makefile make.bat 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-pyc clean-build docs clean 2 | 3 | help: 4 | @echo "clean - remove all build, test, coverage and Python artifacts" 5 | @echo "clean-build - remove build artifacts" 6 | @echo "clean-pyc - remove Python file artifacts" 7 | @echo "clean-test - remove test and coverage artifacts" 8 | @echo "lint - check style with flake8" 9 | @echo "test - run tests quickly with the default Python" 10 | @echo "test-all - run tests on every Python version with tox" 11 | @echo "coverage - check code coverage quickly with the default Python" 12 | @echo "docs - generate Sphinx HTML documentation, including API docs" 13 | @echo "release - package and upload a release" 14 | @echo "dist - package" 15 | @echo "install - install the package to the active Python's site-packages" 16 | @echo "pyinstaller - create executable" 17 | 18 | clean: clean-build clean-pyc clean-test 19 | 20 | clean-build: 21 | rm -fr build/ 22 | rm -fr dist/ 23 | rm -fr out/ 24 | rm -fr .eggs/ 25 | # find . -name '*.egg-info' -exec rm -fr {} + 26 | # find . -name '*.egg' -exec rm -fr {} + 27 | 28 | clean-pyc: 29 | find . -name '*.pyc' -exec rm -f {} + 30 | find . -name '*.pyo' -exec rm -f {} + 31 | find . -name '*~' -exec rm -f {} + 32 | find . -name '__pycache__' -exec rm -fr {} + 33 | 34 | clean-test: 35 | rm -fr .tox/ 36 | rm -f .coverage 37 | rm -fr htmlcov/ 38 | 39 | lint: 40 | flake8 xserverpy tests 41 | 42 | test: 43 | # py.test tests 44 | python setup.py test 45 | 46 | test-all: 47 | tox 48 | 49 | coverage: 50 | coverage run --source xserverpy setup.py test 51 | coverage report -m 52 | coverage html 53 | open htmlcov/index.html 54 | 55 | coverage-no-open: 56 | coverage run --source xserverpy setup.py test 57 | 58 | docs: 59 | rm -f docs/xserverpy.rst 60 | rm -f docs/modules.rst 61 | sphinx-apidoc -o docs/ xserverpy 62 | $(MAKE) -C docs clean 63 | $(MAKE) -C docs html 64 | open docs/_build/html/index.html 65 | 66 | release: clean 67 | python setup.py sdist 68 | python setup.py bdist_wheel 69 | pyinstaller xserverpy.py --onefile -F --distpath out/ 70 | twine upload dist/* 71 | 72 | dist: clean 73 | python setup.py sdist 74 | python setup.py bdist_wheel 75 | ls -l dist 76 | 77 | install: clean 78 | python setup.py install 79 | 80 | pyinstaller: 81 | pyinstaller xserverpy.py --onefile -F --distpath out/ 82 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Xserverpy 2 | 3 | [![Build Status](https://travis-ci.org/oarrabi/xserverpy.svg?branch=master)](https://travis-ci.org/oarrabi/xserverpy) [![Coverage Status](https://coveralls.io/repos/oarrabi/xserverpy/badge.svg?branch=master&service=github)](https://coveralls.io/github/oarrabi/xserverpy?branch=master) 4 | [![PyPI version](https://badge.fury.io/py/xserverpy.svg)](http://badge.fury.io/py/xserverpy) 5 | 6 | Xserverpy makes it possible to use Xcode bots from the command line. 7 |
8 | 9 | ![Preview](https://raw.githubusercontent.com/oarrabi/xserverpy/master/assets/preview.gif) 10 | 11 | # Use cases 12 | - Running Xcode server tasks, like new integration (ie. Build project) or list bots, without the need to install or run Xcode. 13 | - Build Xcode bots from another CI tool like Jenkins (see [Future milestones and improvements](#future-milestones-and-improvements). 14 | - You love ASCII progress bars (or Nikola Tesla's inventions) 15 | 16 | # Installation 17 | 18 | ## Using brew (recommended) 19 | brew tap oarrabi/tap 20 | brew install xserverpy 21 | 22 | ## Using pip 23 | pip install xserverpy 24 | 25 | # Usage 26 | 27 | ## Authentication and Host information 28 | All of xserverpy command accept authentication and Xcode server host/port as flags. For example, in order to list all the bots you would run: 29 | 30 | xserverpy bots --host HOST --port PORT --user USER --pass PASS 31 | 32 | To reduce duplication in calling consequent or future commands, you can run `init` subcommand to store these configuration on your machine. 33 | 34 | xserverpy init --host HOST --port PORT --user USER --pass PASS 35 | 36 | Now that you stored them, you can call all of xcserverpy subcommands without passing these stored arguments: 37 | 38 | xserverpy bots 39 | xserverpy integrations list 40 | 41 | xserverpy init flags: 42 | 43 | --host HOST Xcode server host 44 | --port PORT Xcode server host port, default 443 45 | --user USER Username to use for authentication 46 | --password PASSWORD Password to use for authentication 47 | --local Store configuration file in the local directory 48 | 49 | Note: 50 | - Running `init` sotres a configuration file at `~/.xserverpy`. 51 | - Using `init --local` stores the configuration in the current directory 52 | 53 | ## Bots 54 | List all bots [Demo](http://showterm.io/1e0d25570e5c65ab57cd0) 55 | 56 | xserverpy bots # pass host/user info or load from stored 57 | 58 | ## Integrations 59 | List integrations per bot [Demo](http://showterm.io/5899725079c80c3026d9d) 60 | 61 | xserverpy integrations list --bot 62 | --- 63 | Integrate (build project) [Demo](http://showterm.io/bb69e715ba165d147edf5) 64 | 65 | xserverpy integrations new --bot 66 | --- 67 | Integrate and wait [Demo](http://showterm.io/4b61beb417fe4a5b1ba25) 68 | 69 | xserverpy integrations new --bot --wait 70 | --- 71 | Show running integrations [Demo](http://showterm.io/eae3a3cabf806cc9fd84d) 72 | 73 | xserverpy integrations running 74 | --- 75 | Cancel integrations (build project) [Demo](http://showterm.io/9bbb138149c147ca1c103) 76 | 77 | xserverpy integrations cancel --id 78 | 79 | ## Note on integrate and wait 80 | When using `xserverpy integrations new --wait`, xserverpy keeps polling Xcode server for updates on the running integrations. The default interval is .5s, you can control the behavior and the format of the progress using the following flags: 81 | 82 | --interval INTERVAL Interval to poll the server for updates, default .5s 83 | --no-tty Force non tty progress reporting 84 | 85 | # Future milestones and improvements 86 | - Create Jenkins plugin to embed Xcode server tasks in Jenkins 87 | - Implement show all pending integrations 88 | - Improve code coverage 89 | 90 | # Author 91 | Omar Abdelhafith 92 | [nsomar](http://nsomar.com), [nsomar medium](https://medium.com/@nsomar), [@ifnottrue](https://twitter.com/ifnottrue) 93 | 94 | -------------------------------------------------------------------------------- /assets/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsomar/xserverpy/0ee6a416913a1dcc186d500f54f50a3477a868ea/assets/preview.gif -------------------------------------------------------------------------------- /assets/progress.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsomar/xserverpy/0ee6a416913a1dcc186d500f54f50a3477a868ea/assets/progress.gif -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/xserverpy.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/xserverpy.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/xserverpy" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/xserverpy" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # xserverpy documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Jul 9 22:26:36 2013. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another 20 | # directory, add these directories to sys.path here. If the directory is 21 | # relative to the documentation root, use os.path.abspath to make it 22 | # absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # Get the project root dir, which is the parent dir of this 26 | cwd = os.getcwd() 27 | project_root = os.path.dirname(cwd) 28 | 29 | # Insert the project root dir as the first element in the PYTHONPATH. 30 | # This lets us ensure that the source package is imported, and that its 31 | # version is used. 32 | sys.path.insert(0, project_root) 33 | 34 | import xserverpy 35 | 36 | # -- General configuration --------------------------------------------- 37 | 38 | # If your documentation needs a minimal Sphinx version, state it here. 39 | #needs_sphinx = '1.0' 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 43 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ['_templates'] 47 | 48 | # The suffix of source filenames. 49 | source_suffix = '.rst' 50 | 51 | # The encoding of source files. 52 | #source_encoding = 'utf-8-sig' 53 | 54 | # The master toctree document. 55 | master_doc = 'index' 56 | 57 | # General information about the project. 58 | project = u'xserverpy' 59 | copyright = u'2015, Omar Abdelhafith' 60 | 61 | # The version info for the project you're documenting, acts as replacement 62 | # for |version| and |release|, also used in various other places throughout 63 | # the built documents. 64 | # 65 | # The short X.Y version. 66 | version = xserverpy.__version__ 67 | # The full version, including alpha/beta/rc tags. 68 | release = xserverpy.__version__ 69 | 70 | # The language for content autogenerated by Sphinx. Refer to documentation 71 | # for a list of supported languages. 72 | #language = None 73 | 74 | # There are two options for replacing |today|: either, you set today to 75 | # some non-false value, then it is used: 76 | #today = '' 77 | # Else, today_fmt is used as the format for a strftime call. 78 | #today_fmt = '%B %d, %Y' 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | exclude_patterns = ['_build'] 83 | 84 | # The reST default role (used for this markup: `text`) to use for all 85 | # documents. 86 | #default_role = None 87 | 88 | # If true, '()' will be appended to :func: etc. cross-reference text. 89 | #add_function_parentheses = True 90 | 91 | # If true, the current module name will be prepended to all description 92 | # unit titles (such as .. function::). 93 | #add_module_names = True 94 | 95 | # If true, sectionauthor and moduleauthor directives will be shown in the 96 | # output. They are ignored by default. 97 | #show_authors = False 98 | 99 | # The name of the Pygments (syntax highlighting) style to use. 100 | pygments_style = 'sphinx' 101 | 102 | # A list of ignored prefixes for module index sorting. 103 | #modindex_common_prefix = [] 104 | 105 | # If true, keep warnings as "system message" paragraphs in the built 106 | # documents. 107 | #keep_warnings = False 108 | 109 | 110 | # -- Options for HTML output ------------------------------------------- 111 | 112 | # The theme to use for HTML and HTML Help pages. See the documentation for 113 | # a list of builtin themes. 114 | html_theme = 'default' 115 | 116 | # Theme options are theme-specific and customize the look and feel of a 117 | # theme further. For a list of options available for each theme, see the 118 | # documentation. 119 | #html_theme_options = {} 120 | 121 | # Add any paths that contain custom themes here, relative to this directory. 122 | #html_theme_path = [] 123 | 124 | # The name for this set of Sphinx documents. If None, it defaults to 125 | # " v documentation". 126 | #html_title = None 127 | 128 | # A shorter title for the navigation bar. Default is the same as 129 | # html_title. 130 | #html_short_title = None 131 | 132 | # The name of an image file (relative to this directory) to place at the 133 | # top of the sidebar. 134 | #html_logo = None 135 | 136 | # The name of an image file (within the static path) to use as favicon 137 | # of the docs. This file should be a Windows icon file (.ico) being 138 | # 16x16 or 32x32 pixels large. 139 | #html_favicon = None 140 | 141 | # Add any paths that contain custom static files (such as style sheets) 142 | # here, relative to this directory. They are copied after the builtin 143 | # static files, so a file named "default.css" will overwrite the builtin 144 | # "default.css". 145 | html_static_path = ['_static'] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page 148 | # bottom, using the given strftime format. 149 | #html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | #html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | #html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names 159 | # to template names. 160 | #html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | #html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | #html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | #html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | #html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. 175 | # Default is True. 176 | #html_show_sphinx = True 177 | 178 | # If true, "(C) Copyright ..." is shown in the HTML footer. 179 | # Default is True. 180 | #html_show_copyright = True 181 | 182 | # If true, an OpenSearch description file will be output, and all pages 183 | # will contain a tag referring to it. The value of this option 184 | # must be the base URL from which the finished HTML is served. 185 | #html_use_opensearch = '' 186 | 187 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 188 | #html_file_suffix = None 189 | 190 | # Output file base name for HTML help builder. 191 | htmlhelp_basename = 'xserverpydoc' 192 | 193 | 194 | # -- Options for LaTeX output ------------------------------------------ 195 | 196 | latex_elements = { 197 | # The paper size ('letterpaper' or 'a4paper'). 198 | #'papersize': 'letterpaper', 199 | 200 | # The font size ('10pt', '11pt' or '12pt'). 201 | #'pointsize': '10pt', 202 | 203 | # Additional stuff for the LaTeX preamble. 204 | #'preamble': '', 205 | } 206 | 207 | # Grouping the document tree into LaTeX files. List of tuples 208 | # (source start file, target name, title, author, documentclass 209 | # [howto/manual]). 210 | latex_documents = [ 211 | ('index', 'xserverpy.tex', 212 | u'xserverpy Documentation', 213 | u'Omar Abdelhafith', 'manual'), 214 | ] 215 | 216 | # The name of an image file (relative to this directory) to place at 217 | # the top of the title page. 218 | #latex_logo = None 219 | 220 | # For "manual" documents, if this is true, then toplevel headings 221 | # are parts, not chapters. 222 | #latex_use_parts = False 223 | 224 | # If true, show page references after internal links. 225 | #latex_show_pagerefs = False 226 | 227 | # If true, show URL addresses after external links. 228 | #latex_show_urls = False 229 | 230 | # Documents to append as an appendix to all manuals. 231 | #latex_appendices = [] 232 | 233 | # If false, no module index is generated. 234 | #latex_domain_indices = True 235 | 236 | 237 | # -- Options for manual page output ------------------------------------ 238 | 239 | # One entry per manual page. List of tuples 240 | # (source start file, name, description, authors, manual section). 241 | man_pages = [ 242 | ('index', 'xserverpy', 243 | u'xserverpy Documentation', 244 | [u'Omar Abdelhafith'], 1) 245 | ] 246 | 247 | # If true, show URL addresses after external links. 248 | #man_show_urls = False 249 | 250 | 251 | # -- Options for Texinfo output ---------------------------------------- 252 | 253 | # Grouping the document tree into Texinfo files. List of tuples 254 | # (source start file, target name, title, author, 255 | # dir menu entry, description, category) 256 | texinfo_documents = [ 257 | ('index', 'xserverpy', 258 | u'xserverpy Documentation', 259 | u'Omar Abdelhafith', 260 | 'xserverpy', 261 | 'One line description of project.', 262 | 'Miscellaneous'), 263 | ] 264 | 265 | # Documents to append as an appendix to all manuals. 266 | #texinfo_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | #texinfo_domain_indices = True 270 | 271 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 272 | #texinfo_show_urls = 'footnote' 273 | 274 | # If true, do not generate a @detailmenu in the "Top" node's menu. 275 | #texinfo_no_detailmenu = False 276 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. xserverpy documentation master file, created by 2 | sphinx-quickstart on Tue Jul 9 22:26:36 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to xserverpy's documentation! 7 | ====================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | readme 15 | installation 16 | usage 17 | contributing 18 | authors 19 | history 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | 28 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | At the command line:: 6 | 7 | $ easy_install xserverpy 8 | 9 | Or, if you have virtualenvwrapper installed:: 10 | 11 | $ mkvirtualenv xserverpy 12 | $ pip install xserverpy 13 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\xserverpy.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\xserverpy.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Usage 3 | ======== 4 | 5 | To use xserverpy in a project:: 6 | 7 | import xserverpy 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | wheel==0.23.0 2 | vcrpy 3 | requests 4 | python-dateutil 5 | termcolor 6 | tabulate 7 | pytz 8 | tzlocal 9 | mock 10 | -e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=Package 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from xserverpy.utils import version 4 | 5 | try: 6 | from setuptools import setup 7 | except ImportError: 8 | from distutils.core import setup 9 | 10 | with open('HISTORY.rst') as history_file: 11 | history = history_file.read().replace('.. :changelog:', '') 12 | 13 | requirements = [ 14 | "requests", 15 | "python-dateutil", 16 | "termcolor", 17 | "tabulate", 18 | "pytz", 19 | "tzlocal" 20 | ] 21 | 22 | test_requirements = [ 23 | "vcrpy", 24 | "mock" 25 | ] 26 | 27 | setup( 28 | name='xserverpy', 29 | version=version.VERSION, 30 | description="Python Boilerplate contains all the boilerplate you need to create a Python package.", 31 | author="Omar Abdelhafith", 32 | author_email='o.arrabi@me.com', 33 | url='https://github.com/oarrabi/xserverpy', 34 | packages=[ 35 | 'xserverpy', 36 | 'xserverpy.lib', 37 | 'xserverpy.utils', 38 | 'xserverpy.display', 39 | ], 40 | include_package_data=True, 41 | install_requires=requirements, 42 | license="BSD", 43 | zip_safe=False, 44 | keywords='xserverpy', 45 | classifiers=[ 46 | 'Development Status :: 2 - Pre-Alpha', 47 | 'Intended Audience :: Developers', 48 | 'License :: OSI Approved :: BSD License', 49 | 'Natural Language :: English', 50 | "Programming Language :: Python :: 2", 51 | 'Programming Language :: Python :: 2.6', 52 | 'Programming Language :: Python :: 2.7', 53 | 'Programming Language :: Python :: 3', 54 | 'Programming Language :: Python :: 3.3', 55 | 'Programming Language :: Python :: 3.4', 56 | ], 57 | test_suite='tests', 58 | entry_points={ 59 | 'console_scripts': [ 60 | 'xserverpy=xserverpy.xserverpy:start', 61 | ], 62 | }, 63 | tests_require=test_requirements 64 | ) 65 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /tests/bots_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import vcr 3 | from xserverpy.lib.bots import Bots 4 | from xserverpy.lib.xcode_server import XcodeServer 5 | from xserverpy.lib.user import User 6 | from xserverpy.utils.settings import Settings 7 | 8 | 9 | class TestBots(unittest.TestCase): 10 | 11 | def setUp(self): 12 | user = User(user="aa", password="pass") 13 | server = XcodeServer(host="https://127.0.0.1") 14 | self.settings = Settings(server, user) 15 | 16 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/bots.yaml') 17 | def test_can_request_bots(self): 18 | b = Bots(self.settings) 19 | assert len(b.get_all()) == 1 20 | 21 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/bot.yaml') 22 | def test_can_request_1_bot(self): 23 | b = Bots(self.settings) 24 | item = b.get_item(item_id="88c98ee21f3895749ec3888b930017be") 25 | self.assertEqual(item.name, "Testbots Bot") 26 | 27 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/bots.yaml') 28 | def test_can_request_bots_with_name(self): 29 | b = Bots(self.settings) 30 | bot = b.get_named("Testbots Bot") 31 | self.assertIsNotNone(bot) 32 | 33 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/bots.yaml') 34 | def test_returns_none_if_not_found(self): 35 | b = Bots(self.settings) 36 | bot = b.get_named("sss") 37 | self.assertIsNone(bot) 38 | -------------------------------------------------------------------------------- /tests/cli_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import vcr 3 | from xserverpy.lib.cli import parse 4 | from xserverpy.xserverpy import start_with_args 5 | from cStringIO import StringIO 6 | import sys 7 | 8 | 9 | class Capturing(list): 10 | def __enter__(self): 11 | self._stdout = sys.stdout 12 | sys.stdout = self._stringio = StringIO() 13 | return self 14 | 15 | def __exit__(self, *args): 16 | self.extend(self._stringio.getvalue().splitlines()) 17 | sys.stdout = self._stdout 18 | 19 | 20 | class CliTests(unittest.TestCase): 21 | 22 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/bdd_integrations.yaml') 23 | def test_can_list_integrations(self): 24 | args = ["integrations", "list", 25 | "--host", "https://127.0.0.1", 26 | "--bot", 'Testbots Bot', 27 | "--user", 'Omar Abdelhafith', 28 | "--pass", "omaromar123"] 29 | 30 | sys.argv = args 31 | out = "" 32 | with Capturing() as output: 33 | start_with_args(args) 34 | out = output 35 | 36 | self.assertIn("Listing all integrations for bot 'Testbots Bot'", out) 37 | 38 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/bdd_new_integration.yaml') 39 | def test_can_create_integration(self): 40 | args = ["integrations", "new", 41 | "--host", "https://127.0.0.1", 42 | "--bot", 'Testbots Bot', 43 | "--user", 'Omar Abdelhafith', 44 | "--pass", "omaromar123"] 45 | 46 | sys.argv = args 47 | out = "" 48 | with Capturing() as output: 49 | start_with_args(args) 50 | out = output 51 | 52 | self.assertIn("Integration number '142' for bot 'Testbots Bot' posted successfully", out) 53 | 54 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/bdd_bots.yaml') 55 | def test_can_list_bots(self): 56 | args = ["bots", 57 | "--host", "https://127.0.0.1", 58 | "--user", 'Omar Abdelhafith', 59 | "--pass", "omaromar123"] 60 | 61 | sys.argv = args 62 | out = "" 63 | with Capturing() as output: 64 | start_with_args(args) 65 | out = output 66 | 67 | self.assertIn("Testbots Bot 88c98ee21f3895749ec3888b930017be" + 68 | " 139", out) 69 | 70 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/integration_running.yaml') 71 | def test_can_list_running_integrations(self): 72 | args = ["integrations", "running", 73 | "--host", "https://127.0.0.1", 74 | "--user", 'Omar Abdelhafith', 75 | "--pass", "omaromar123"] 76 | 77 | sys.argv = args 78 | out = "" 79 | with Capturing() as output: 80 | start_with_args(args) 81 | out = output 82 | 83 | self.assertIn("1 Integrations running currently", out) 84 | -------------------------------------------------------------------------------- /tests/fixtures/vcr_cassettes/bdd_bots.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Authorization: [Basic T21hciBBYmRlbGhhZml0aDpvbWFyb21hcjEyMw==] 8 | Connection: [keep-alive] 9 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 10 | method: GET 11 | uri: https://127.0.0.1/xcode/api/bots 12 | response: 13 | body: 14 | string: !!binary | 15 | H4sIAAAAAAAAA+1YWW/cNhD+K4aerTVFUQf9prM12iaG10kKBIEhUdQuG0lUKcnN1tj/3qG0h7Nx 16 | k3XQFmm7fjFMzvHNoRl+fjCYHJreuMTnhuLdUPWdcfn2wbgThXFp+D6jPufYKm2fOh6hnNm+7+fU 17 | Rsjycm6cG3eK34Mk9hzTyTxcWjn3fS+nTslcajk0Q66FrIy7HgLphZJDa1w+GE1Wc1AL3DDFdopN 18 | mqSxSVKHmtRxXTNyY+JR7JKIhsb6HEA2pVgMKuuFbLR+PoiqT5Wso4pncILOjZYrIQvB5mzJi6Hi 19 | V03P1X1WjZdMFjyS91xlC36teMkVbxgfwwa9Uqq6u+VdH7DJQZlVHT83eiUWC650St7tBYMmq1a/ 20 | 8wPZDtzW/MUUl7aVS8jlucE/tFL13bWSxcBGyIFiS3EPYr0atBOQFc0i5veC8at446wY/5y3nIlS 21 | sF3cJYQ9AXow2irrNSB9PtWLeASK5RMfWdzzXcoxzm3CbOwjZGPbBziF6EBvtcEpXs73NSRmxrCF 22 | CLFw6VolKhxsY7dwc690cl4gruvdiXoAv1JdFbzpARtXoMpkPcvatuKzLaiZaJey4TtxUBVHaUid 23 | NF3e4sVQ56OsZQfERs4CLqCC3ZgKg86Q7owpIberlo91znRue876QfHN4XqXzT3kMcmg/Rvn76vV 24 | tmPibDUaqUUzQFWCEix/Lwd1K3UvLaD7JifdRn5yYJ8bSxB6WW6FRnzoUb9M9d72y0HV9a9RZxdD 25 | B9YYdGvTK1mF1cBbJfQn+mDEr2/njy/fSPW+azPGd1I/yqlXuh/4SmsgkiDbD0PPsq3Qi2MSWTR1 26 | HJqQNApQGCaJFyMvIk9ZD1XWsOU+aaNJo846SAuU4mnxl+3ePbbIp2J/jlknYPLxtGljvT7G3LUS 27 | daZWN7yWPb/hrewE9N9qsnx0Po7xpE+ghpFsV3s/BxU4CvIh1mDolzrrk6V5rztvIfhfUdTnOv9M 28 | UbJOsCeRro6s1KP8zXtQfH586Cg/hy1MLIJxiIgZBkliEmrHZujTwPSQFyZRYMUY4+c2wHXWL5+P 29 | fxwDd3pTXBhHhaLn9hTEoxXzZbXX27GJkX1cQ8JQhpmlo7qVsLt+gZH6sd/ZB71TW7g6CsFBl22b 30 | +e3XdOirmx8nLMu+b7vLi4uF6JdDPoOdciEzpbJcXOwSO4PLr0G4mq9g0NWTo/22KvrZzzrw2TSl 31 | 2WRt9t1XOjlszaNbZ/1Of2OK/zoIeLe9amHxFHz3GGk+foachVLD68cNY8Em3m+qu/H9p9esRWD3 32 | FJLdTWJGPumIZnUV6xceQSSM9BOigg1wAwtVd9S/Zz1tXW+RT+JFjn3PLUrfZTAHPEpcz+NlUTCP 33 | UloSxuHO8pB71L5z0H963X3Sq4mTUCvApmv5oUkwTU2Yp44ZWEkYucRFhCanMXoao9/8GD2G7vIc 34 | sz1VckyLObnvO56fA8Mqge66KLOxh3y39Ejmek+Q3SRygzCNiBnHHpBdO07MILItkwaB5SapFQa+ 35 | /beSXXQs2QVuucy6iR53TIm2D2Wh87pbIni3Y15ISPzqLJJ1LXrNTc9kc5ZmogIGZozBFGIckTqS 36 | Dp55Qzeqy+ZNphr47LsdAlgnmvclSkkNYyJKshljA869Pd9Jb4i4esKQBgBHOsxHluYDY7zbykGq 37 | eQ1y0WG+RcOqQadQh/QTKEAed1ayYoonq26Am7cCGm9D2TfGtnnYKmysXXXdwGPeg8zmar3+p/+p 38 | cKK7J7p7orv/N7rrhiEJwzQwLRtRk1hRZAYxIaYD2ypMUhz6zrMb4PROO73Tvjm6m8PPF2ku/hzJ 39 | DT0/TePgRHJPJHeT6sAOI5/g2PTc0DOJFxOT0pSaNrJxSl2KXfs0PE/D85sfnu/WfwDLHrO36x0A 40 | AA== 41 | headers: 42 | connection: [Keep-Alive] 43 | content-encoding: [gzip] 44 | content-type: [application/json] 45 | date: ['Fri, 24 Jul 2015 23:43:51 GMT'] 46 | keep-alive: ['timeout=5, max=100'] 47 | ms-author-via: [DAV] 48 | server: [Apache] 49 | set-cookie: ['session=s%3Ao2XJF-1Zm4mQajGoSOJOweS7sFT-p6R8.iawO5WNkk9K0fcs0uB0GvcB9Glfg%2FopITBKoSqx8jyk; 50 | Path=/; Expires=Sat, 25 Jul 2015 23:43:51 GMT; HttpOnly; Secure'] 51 | vary: [Accept-Encoding] 52 | x-xcsapiversion: ['3'] 53 | x-xcsresponse-status-title: [OK] 54 | x-xcsresultslist: ['true'] 55 | status: {code: 200, message: OK} 56 | version: 1 57 | -------------------------------------------------------------------------------- /tests/fixtures/vcr_cassettes/bdd_cancel_integration.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Authorization: [Basic T21hciBBYmRlbGhhZml0aDpvbWFyb21hcjEyMw==] 8 | Connection: [keep-alive] 9 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 10 | method: GET 11 | uri: https://127.0.0.1/xcode/api/bots 12 | response: 13 | body: 14 | string: !!binary | 15 | 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 40 | AA== 41 | headers: 42 | connection: [Keep-Alive] 43 | content-encoding: [gzip] 44 | content-type: [application/json] 45 | date: ['Sat, 25 Jul 2015 00:08:26 GMT'] 46 | keep-alive: ['timeout=5, max=100'] 47 | ms-author-via: [DAV] 48 | server: [Apache] 49 | set-cookie: ['session=s%3AWPUHkJmzgG-z-aCXP16rEQ1B84TEG0jn.hYBBLt%2BMhC4W%2Fq1K7%2FLP22EDpQWXSaY2xum1SrrW8qI; 50 | Path=/; Expires=Sun, 26 Jul 2015 00:08:26 GMT; HttpOnly; Secure'] 51 | vary: [Accept-Encoding] 52 | x-xcsapiversion: ['3'] 53 | x-xcsresponse-status-title: [OK] 54 | x-xcsresultslist: ['true'] 55 | status: {code: 200, message: OK} 56 | - request: 57 | body: null 58 | headers: 59 | Accept: ['*/*'] 60 | Accept-Encoding: ['gzip, deflate'] 61 | Authorization: [Basic T21hciBBYmRlbGhhZml0aDpvbWFyb21hcjEyMw==] 62 | Connection: [keep-alive] 63 | Content-Length: ['0'] 64 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 65 | method: POST 66 | uri: https://127.0.0.1/xcode/api/bots/88c98ee21f3895749ec3888b930017be/integrations 67 | response: 68 | body: 69 | string: !!binary | 70 | H4sIAAAAAAAAA+1XbW/bNhD+K4M+Rw5JUW/5Zkn2FqxogzjthhVFQFEnm6ssqpSU1Svy33eU/FYv 71 | W51iH7ahnwSJ9/Lc3aO74yfnXhXOlRNFMo4AGC29KPZDHoP0oijKY4/RopDEuXDuDTygpOd6eSB8 72 | GXglCFFEgkYFL8MyDCOPBVTQEmVz3TlXn75omxAa5nBkmwRuzKmkQHMviCU+ipwAycOCAQial0WA 73 | 0kuj+8bar8UaUG0aJHPmzZkbz+aZy+d+7MZ+ELhpkPEwZgFP48R5vHCkrku17I3olK6tft6rqpsb 74 | vU4rEPiFXDgNGKULJRdyBUVfwXXdgXkQ1XAodQGpfgAjlnBjoAQDtUQEbNArtVm3d9B2Uzk6KEXV 75 | woXTGbVcgmmdq7fvDoLTWlSb3+FEtkW3a3g5xmVtYSZbDBk+Ntp07Y3RRS8HyFMjV+oBxTrTWyco 76 | q+plBg9KwnW2dVYMr4sGpCqV3MddYtgjoE9OU4nOAjrUi4ccixXxiFAIoyAGxnKPS49FhHjMixBO 77 | oVrU22xxqleLQw25KySjhHPKyoCWpPAZ8qII8rD0cygI2Hq3at2jX22uC6g7xAYGVaVeT0TTVDDZ 78 | gZqoZqVr2IujqjpLQ9uk2fIWL/t1PshSb8o94i/xACvYDqlw4gmxzBgTcrdpYKizsLntQHa9ge3H 79 | x302D5CHJKP2bwDvq82OMZnYDEbWqu6xKtMSLf+ge3OnLZeWyL7RSbuVHx14F84KhV6VO6EBHzni 80 | y1jvHV9Oqm4fg84+hhatSWRr3RldJVUPjVH18Fdmb+4Wx4c/afO+bYSEvdQLPXKl/RE2VoPwGfGi 81 | JAmpR5Mwy3hK47nvxzM+T6ckSWazMCNhyp+ynhhRy9UhaYNJZy1aTAuW4mnxV83BPaP8z2J/jdkm 82 | YPTxtGnn8fEcczdGrYXZ3MJad3ALjW4V8m8zWj47H+d4sl+whqluNgc/JxU4C/Ip1mnfrWzWR0uL 83 | zjJvqeCfKOpznf9NUUSr5JNIN2dW6ih/iw4Vnx8fOcvPKYU55YwlhLvJdDZzeexlbhLFUzckYTJL 84 | pzRjjD2XADeiWz0f/9AG7u2kuHTOCsX27TGIoxHzZbU3u7bJiHceIbEpY8+yUd1pnF2/Ykv93O/k 85 | o52pDR6dheCEZTsyv/0ahr6+fTFiWXVd015dXi5Vt+rzCc6USy2MEbm63Cd2godfg3Cz2GCjW4+O 86 | DtOq6CY/28AnY5eWo7XJ91/p5JSaZ1Pn8Z39xwx86JWB9nWDg6eA/TJSf76GfJdoC68bJgzFSXyY 87 | VPdS93ZRwu9+gINSy/tRbNgEUUfVm+sMXxknPEntClHhBLjFgWoZ9d8ZTzvXO+SjeJGzKAyKMgok 88 | 9oEw5kEYQol7cxjHcckl4BkNSXDWvPPJ/3rcnaY6TtMU+6bvzr00dvksTXGNTzOX+TxKPUpmPE2+ 89 | tdFvbfTf30ZbXOCrYnuV3LZQ2Ru8I3aLDvDCirt8XSBFEVy9vZUM7fJDDz0U2XAxcBihvktC5P8d 90 | IVckumLBxI/pL/bW1EsJbXvfdgbE+2HLP+q0R+34s44bU05ivOX8AU6BwtfrDwAA 91 | headers: 92 | connection: [Keep-Alive] 93 | content-encoding: [gzip] 94 | content-type: [application/json] 95 | date: ['Sat, 25 Jul 2015 00:08:26 GMT'] 96 | keep-alive: ['timeout=5, max=100'] 97 | location: ['https://127.0.0.1:20343/integration/88c98ee21f3895749ec3888b9321ddc0'] 98 | ms-author-via: [DAV] 99 | server: [Apache] 100 | set-cookie: ['session=s%3APPiSOld1DsEM3h2XZepuHX4zGIaQi436.%2F73pei%2BKeeTK3Fy57h9iH5dbuAHJ1GwqzVEtlTwZ4Hs; 101 | Path=/; Expires=Sun, 26 Jul 2015 00:08:26 GMT; HttpOnly; Secure'] 102 | vary: ['X-HTTP-Method-Override,Accept-Encoding'] 103 | x-xcsapiversion: ['3'] 104 | x-xcsresponse-status-title: [Created] 105 | status: {code: 201, message: Created} 106 | - request: 107 | body: null 108 | headers: 109 | Accept: ['*/*'] 110 | Accept-Encoding: ['gzip, deflate'] 111 | Authorization: [Basic T21hciBBYmRlbGhhZml0aDpvbWFyb21hcjEyMw==] 112 | Connection: [keep-alive] 113 | Content-Length: ['0'] 114 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 115 | method: POST 116 | uri: https://127.0.0.1/xcode/api/integrations/Testbots%20Bot/cancel 117 | response: 118 | body: {string: !!python/unicode ''} 119 | headers: 120 | connection: [Keep-Alive] 121 | content-length: ['0'] 122 | content-type: [application/json] 123 | date: ['Sat, 25 Jul 2015 00:08:27 GMT'] 124 | keep-alive: ['timeout=5, max=100'] 125 | ms-author-via: [DAV] 126 | server: [Apache] 127 | set-cookie: ['session=s%3AmPV5XPgy6hkSuq3luqo0_rZRHwl654LV.2Jf%2FyacnZseW9ITQ7VhmY3ucjI8yQkbGzrewk%2BM%2BgE0; 128 | Path=/; Expires=Sun, 26 Jul 2015 00:08:27 GMT; HttpOnly; Secure'] 129 | vary: ['X-HTTP-Method-Override,Accept-Encoding'] 130 | x-xcsapiversion: ['3'] 131 | x-xcsresponse-status-message: ['Not found: [dbCoreClass - xcsDBCoreClassFindDocumentWithUUIDInCouchDB] 132 | error retrieving document ''Testbots Bot'': missing'] 133 | x-xcsresponse-status-title: [Not Found] 134 | status: {code: 404, message: Not Found} 135 | version: 1 136 | -------------------------------------------------------------------------------- /tests/fixtures/vcr_cassettes/bdd_integrations.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Authorization: [Basic T21hciBBYmRlbGhhZml0aDpvbWFyb21hcjEyMw==] 8 | Connection: [keep-alive] 9 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 10 | method: GET 11 | uri: https://127.0.0.1/xcode/api/bots 12 | response: 13 | body: 14 | string: !!binary | 15 | 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 40 | AA== 41 | headers: 42 | connection: [Keep-Alive] 43 | content-encoding: [gzip] 44 | content-type: [application/json] 45 | date: ['Fri, 24 Jul 2015 23:39:37 GMT'] 46 | keep-alive: ['timeout=5, max=100'] 47 | ms-author-via: [DAV] 48 | server: [Apache] 49 | set-cookie: ['session=s%3AtUGuxcprhm2MEeLBHTzeWBJkmqEaTTLd.DBAsBJPVoP1hiBi%2BVG9eJzz1%2FinQIuD9DBdUnuMtU68; 50 | Path=/; Expires=Sat, 25 Jul 2015 23:39:37 GMT; HttpOnly; Secure'] 51 | vary: [Accept-Encoding] 52 | x-xcsapiversion: ['3'] 53 | x-xcsresponse-status-title: [OK] 54 | x-xcsresultslist: ['true'] 55 | status: {code: 200, message: OK} 56 | - request: 57 | body: null 58 | headers: 59 | Accept: ['*/*'] 60 | Accept-Encoding: ['gzip, deflate'] 61 | Authorization: [Basic T21hciBBYmRlbGhhZml0aDpvbWFyb21hcjEyMw==] 62 | Connection: [keep-alive] 63 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 64 | method: GET 65 | uri: https://127.0.0.1/xcode/api/bots/88c98ee21f3895749ec3888b930017be/integrations 66 | response: 67 | body: 68 | string: !!binary | 69 | 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 141 | oKGuzx1qJxv47er/AaW8lneABAEA 142 | headers: 143 | connection: [Keep-Alive] 144 | content-encoding: [gzip] 145 | content-type: [application/json] 146 | date: ['Fri, 24 Jul 2015 23:39:37 GMT'] 147 | keep-alive: ['timeout=5, max=100'] 148 | ms-author-via: [DAV] 149 | server: [Apache] 150 | set-cookie: ['session=s%3AMOaXpRxHhujf8QaauQumWGbcbsolOGFN.SRchbZ2P45hO%2BgowVu5%2Bl0YYlFQOvoZGAca77WPMbq8; 151 | Path=/; Expires=Sat, 25 Jul 2015 23:39:37 GMT; HttpOnly; Secure'] 152 | vary: [Accept-Encoding] 153 | x-xcsapiversion: ['3'] 154 | x-xcsresponse-status-title: [OK] 155 | x-xcsresultslist: ['true'] 156 | status: {code: 200, message: OK} 157 | version: 1 158 | -------------------------------------------------------------------------------- /tests/fixtures/vcr_cassettes/bdd_new_integration.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Authorization: [Basic T21hciBBYmRlbGhhZml0aDpvbWFyb21hcjEyMw==] 8 | Connection: [keep-alive] 9 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 10 | method: GET 11 | uri: https://127.0.0.1/xcode/api/bots 12 | response: 13 | body: 14 | string: !!binary | 15 | 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 40 | AA== 41 | headers: 42 | connection: [Keep-Alive] 43 | content-encoding: [gzip] 44 | content-type: [application/json] 45 | date: ['Fri, 24 Jul 2015 23:46:30 GMT'] 46 | keep-alive: ['timeout=5, max=100'] 47 | ms-author-via: [DAV] 48 | server: [Apache] 49 | set-cookie: ['session=s%3AW7mf_fy-37wmacEMZNZ2499COGwA2LxI.jmbYNtDGXOHYG7jX1RKIZ%2BKyNh1rVVzwE7dmBhlUbd4; 50 | Path=/; Expires=Sat, 25 Jul 2015 23:46:30 GMT; HttpOnly; Secure'] 51 | vary: [Accept-Encoding] 52 | x-xcsapiversion: ['3'] 53 | x-xcsresponse-status-title: [OK] 54 | x-xcsresultslist: ['true'] 55 | status: {code: 200, message: OK} 56 | - request: 57 | body: null 58 | headers: 59 | Accept: ['*/*'] 60 | Accept-Encoding: ['gzip, deflate'] 61 | Authorization: [Basic T21hciBBYmRlbGhhZml0aDpvbWFyb21hcjEyMw==] 62 | Connection: [keep-alive] 63 | Content-Length: ['0'] 64 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 65 | method: POST 66 | uri: https://127.0.0.1/xcode/api/bots/88c98ee21f3895749ec3888b930017be/integrations 67 | response: 68 | body: 69 | string: !!binary | 70 | H4sIAAAAAAAAA+1XbW/bNhD+K4M+Rw5FUaKYb5Jlb8GKNojTblhRBBJ5srXaokpJWbUi/31HyW/1 71 | stUp9mEb+sk2eS/P3T2+O35y7kvlXDlRJEUEQL3Cj0TAmQDpR1GUC9/D05w7F869gQeU9F0WkNwL 72 | 80IEfs4491RBA8FCDiJQkfIilM1161x9+qJtQjyew8E25ZFLZJgpyhXnVOG3LJcyDyX3uVB+EfAA 73 | pZdGd7W1X2UbQLU4TObUn1NXzOapy+aBcEUQhu40TBkXNGRTkTiPF47UVVEuO5O1pa6sft6V63Zu 74 | 9Ga6hgxPyIVTgym1KuVCrkB1a7iuWjAP2Xq4lFrBVD+AyZZwY6AAA5VEBHTQK7TZNHfQtLEcHRTZ 75 | uoELpzXlcgmmca7evjsIxlW27n+HE9kG3W7g5RiXtYWZbDBk+Fhr0zY3RqtODpBjI1flA4q1prNO 76 | ULaslik8lBKu060zNfxc1CDLopT7uAsMewT0yanXWWsBHerFOMNiRSwiHvAoFEBp7jPp04gQn/q2 77 | vqpsUK/f4ixfLQ41ZG4mqUcY82gRegVRAfVpqMKcF0EOioCtd1NuOvSrzbWCqkVsYFBV6s0kq+s1 78 | THagJmW90hXsxVG1PEtD26TZ8qqX3SYfZD0/Zj4JlniBFWyGVDhiQiwzxoTc9TUMdc5sbluQbWdg 79 | e/i4z+YB8pBk1P4N4P263zEmzfrByKasOqxKXKDlH3Rn7rTl0hLZNzpptvKjA//CWaHQq2InNOAj 80 | R3wZ673jy0nV7cegs4+hQWsS2Vq1Rq+TdQe1KavhX5m+uVscX/6kzfumziTspV7okSvNj9BbDcJm 81 | xI+ShHu+l/A0ZVNPzINAzNh8GpMkmc14SviUPWU9MVklV4ekDSadTdZgWrAUT4u/qg/uqcf+LPbX 82 | mG0CRh9Pm3YeH88xd2PKTWb6W9joFm6h1k2J/OtHy2fn4xxP9gRrONV1f/BzUoGzIJ9ijbt2ZbM+ 83 | Wlq0lnnLEv6Joj7X+d8UJWtK+STS/sxKHeVv0aLi8+MjZ/k5pTDzGKUJYW4Sz2YuE37qJpGIXU54 84 | MpvGXkopfS4BbrJ29Xz8Qxu4t5Pi0jkrFNu3xyCORsyX1d7s2iYl/nmExKaMPctGdadxdv2KLfVz 85 | v5OPdqbWeHUWghOW7cj89msY+vr2xYhl1bZ1c3V5uSzbVZdPcKZc6syYLC8v94md4OXXIOwXPTa6 86 | zejoMK1UO/nZBj4Zu7QcrU2+/0onp9Q8mzqP7+x/zMCHrjTQvK5x8CjYLyPV52vId4m28Nphwng4 87 | iQ+T6l7qzi5KeM5wGVJa3o9iwyaIOmXVX6d2w2OEJVO7QqxxAtziQLWM+u+Mp53rHfJRXOU04qEq 88 | olBiH+B2GeZQKCW5EKJgEvDO4yQ8a94F5H897k5THcVJKngcufOEC5cRmriREJHrRzN8W8QipU/m 89 | 7Vsb/dZG/2VttMEFfq22T8ltC5WdwTdiu2gBH6y4y1cKKYrgqu2rZGiXHzroQKXDw8ChxAtcwl3K 90 | 7qh/xcIrn0x4yH6xr6ZOSmia+6Y1kL0ftvyjTnvUjo87bux5QerFzuMfpIdM6usPAAA= 91 | headers: 92 | connection: [Keep-Alive] 93 | content-encoding: [gzip] 94 | content-type: [application/json] 95 | date: ['Fri, 24 Jul 2015 23:46:31 GMT'] 96 | keep-alive: ['timeout=5, max=100'] 97 | location: ['https://127.0.0.1:20343/integration/88c98ee21f3895749ec3888b931ee2b7'] 98 | ms-author-via: [DAV] 99 | server: [Apache] 100 | set-cookie: ['session=s%3AdYPYyOyJ1I1PYfmPVKNqRURDldyD1ag3.9fqdwyO76%2FhusWUCmK0VaB5n4Q%2F5Ia9wCbBREbKpEK8; 101 | Path=/; Expires=Sat, 25 Jul 2015 23:46:30 GMT; HttpOnly; Secure'] 102 | vary: ['X-HTTP-Method-Override,Accept-Encoding'] 103 | x-xcsapiversion: ['3'] 104 | x-xcsresponse-status-title: [Created] 105 | status: {code: 201, message: Created} 106 | version: 1 107 | -------------------------------------------------------------------------------- /tests/fixtures/vcr_cassettes/bot.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Connection: [keep-alive] 8 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 9 | method: GET 10 | uri: https://127.0.0.1/xcode/api/bots/88c98ee21f3895749ec3888b930017be 11 | response: 12 | body: 13 | string: !!binary | 14 | H4sIAAAAAAAAA+1WbW/bNhD+K4M+Rw5FUW/5phd7C1a0QZx2A4oikKiTzVUWNYpKqwX57ztKsZ16 15 | 2aYU+7AN/WSIvLvnubvHd7y3bkVpXVhhyKMQgDqVG0ZewCLgbhiGReQS4gQFWGfWrYI7tHSInTOn 16 | CgMGfu4BeMwr8Sd3CxIRToFVFRpvlOxb6+LeavIdoFfsJyvqrqgdLVeZzVZeZEee79upn7Egoj5L 17 | o8R6OLO4bCqx6VWuhWyMf9GLWq+U3KU15HhCzqwWlJCl4Gu+hbKv4bLRoO7yerzksoRU3oHKN3Cl 18 | oAIFDUcGdPSrpNp1N9DpmE8AVV53cGZpJTYbUJ118f7D0TBu8nr4DU5sO4TdwespLxOrkLrDlOFz 19 | K5XurpQsez5SjhXfijs006o3IGgrmk0Gd4LDZfYIVo6f6xa4qAQ/5F1h2hOhe6utc20ImfOpXQyr 20 | T52QhcSBIPQjoLRwGXdpSIhL3RDplKJDv+GRp3izPraQ2TmnDmHMoZXvVKT0qEv90i+CyiugJGDa 21 | 3Yldj7hSXZbQaOQGCl253C3ytq1hsSe1EO1WNnAwR1cxy0Oaopn2lq/7XTHaOm7MXOJt8AI72I2l 22 | sKIFMcqYCnIztDD2OTe11cB1r+Dx8OFQzSPlscjo/QngYz3sFZPlwxhkJ5oeuxJXGPkH2asbabS0 23 | QfVNIN2j/QTgnllbNHpT7Y1GfuSJXqZ+7/Vy0nXzM/occugwGke1NlrJOql7aJVotOly9u5m/fTy 24 | J6k+dm3O4WD1Sk5a6X6EwXgQtiRumCSB4zpJkGUsdaKV50VLtkpjkiTLZZCRIGXPRU9U3vDtsWhj 25 | SGuXd1gWbMXz5m/aIzx12B/N/pyzKcCE8Xxo6+FhTrgrJXa5Gq5hJzVcQys7gfobpsiz6zEHyZxg 26 | D1PZDkeckw7MonzKNe711lR9irTWRnkbAf9EU18K/hdNyTvBn2U6zOzUk/qtNTq+PD8yC+dUwsxh 27 | lCaE2Um8XNoscjM7CaPYDkiQLNPYySilLxXAVa63L+c/joFbsynOrVmpmLk9JfFkxfy927v92KTE 28 | nSdIHMo4s0xWNxJ31y84Ur/EXXw2O7XFq1kMTlS2F/P7r1Ho2+tXE5et1m13cX6+EXrbFwvcKecy 29 | VyovxPmhsAu8/BqGw3rAQbebgI7bqtSLn03ii2lK8yna4vuvBDmV5mzpPHww/zEFv/ZCQfe2xcVT 30 | wuEx0nz5DPkukYaeHjeMg5v4uKluuezNQ8m68HBNSn47GVnF5CGa4TLDT8oIS1LzgKhx/l/jOjV6 31 | +u8spz30nvlkXhY0DPyyCn2OUyCImB8EUJUlD6IoqhgHvHMC4s/adh75Xy+701J7cUo8l1KbrvzQ 32 | Zo67ssMsXdqrlIU0jNMscaJvQ/TbEP23D9HfAS/xhtDrDgAA 33 | headers: 34 | connection: [Keep-Alive] 35 | content-encoding: [gzip] 36 | content-type: [application/json] 37 | date: ['Thu, 23 Jul 2015 21:10:44 GMT'] 38 | keep-alive: ['timeout=5, max=100'] 39 | ms-author-via: [DAV] 40 | server: [Apache] 41 | set-cookie: ['session=s%3AX9lb6zxq1nDP4q8goW97EY33XiaQNa_b.0qNHTyrjKuxYcqtlWwaj9YrtPl37UGIl%2FUrwVYizjnM; 42 | Path=/; Expires=Fri, 24 Jul 2015 21:10:44 GMT; HttpOnly; Secure'] 43 | vary: [Accept-Encoding] 44 | x-xcsapiversion: ['3'] 45 | x-xcsresponse-status-title: [OK] 46 | status: {code: 200, message: OK} 47 | version: 1 48 | -------------------------------------------------------------------------------- /tests/fixtures/vcr_cassettes/bots.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Connection: [keep-alive] 8 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 9 | method: GET 10 | uri: https://127.0.0.1/xcode/api/bots 11 | response: 12 | body: 13 | string: !!binary | 14 | H4sIAAAAAAAAA+1W226kRhD9lYjnYdw0zc1vXGYSK6tdy+PdRFpZFjTFTGeBJg14l1j+91SDZ8Y7 15 | cRJs5SGJ9glBV9U5VXWo6nuDy77ujHNrYSho+7JrjfOP98atyI1zw/d54ANQq7D9wPFYANz2fT8L 16 | bEIsLwNjYdwquENLi5gpswrfY+CmDoDDnBwfqZ2RgHAKrCjQeKtk3xjn90adVoBeoRutqb2mZrBa 17 | JyZbO4EZOK5rxm7CvIC6LA4i42GBHOtCbHuVdkLW2j/rRdmtlaziElL8QhZGA0rIXPAN30Hel3BR 18 | d6Du0nI85DKHWN6BSrdwqaAABTVHBnT0K6Sq2mtou5BPAEVatrAwOiW2W1C6IjdHw7BOy+E3OLFt 19 | EbaCt1NeOlYmsZQLA740UnXtpZJ5z0fKoeI7cYdmneo1CNqKepvAneBwkTyC5ePrpgEuCsEPeReY 20 | 9kTo3mjKtNOE9PepXQyrTy2f+cQCz3cDoDSzGbepT4hNbR/p5KJFv+GRp3i3ObaQmSmnFmHMooVr 21 | FSR3qE3d3M28wskgJ6Db3YqqR1ypLnKoO+QGCl25rJZp05Sw3JNaimYnaziYo6uY5SF10XR787d9 22 | lY22lh0ymzhbPMAOtmMpjGBJtDKmglwPDYx9TnVtO+Bdr+Dx48OhmkfKY5HR+zPAp3LYKyZJhzFI 23 | JeoeuxIWGPkH2atrqbW0RfVNIO2j/QRgL4wdGr0r9kYjP/JEL1O/93o56bp+jD6HHFqMxlGtdadk 24 | GZU9NEroP/TeSD5cb54e/iTVp7ZJORys3shJK+2PMGgPwlbE9qPIs2wr8pKExVawdpxgxdZxSKJo 25 | tfIS4sXsueiRSmu+OxZtDGlUaYtlwVY8b/6uOcJTi/3R7M856wJMGM+HNh4e5oS7VKJK1XAFlezg 26 | ChrZCtTfMEWeXY85SPoL9jCWzXDEOenALMqnXMO+2+mqT5E2nVbeVsA/0dSXgv9FU9JW8GeZDjM7 27 | 9aR+mw4dX54fmYVzKmFmMUojwswoXK1MFtiJGflBaHrEi1ZxaCWU0pcK4DLtdi/nP46BW70pzoxZ 28 | qei5PSXxZMX8vduH/dikxJ4nSBzKOLN0VtcSd9cvOFK/xl1+0Tu1waNZDE5Uthfzx9co9P3Vm4nL 29 | ruua9vzsbCu6XZ8tcaecyVSpNBNnh8Iu8fA1DIfNgIOumoCO2yrvlj/rxJfTlOZTtOX3rwQ5leZs 30 | 6Tzc6H9Mwa+9wGvb+wYXTw6Hy0j99TXku0hqet24YfCiJ46b6na8/uk16+CalPx2MjKyyUPUw0WC 31 | r5QRFsX6AlHi/L/Cdar19N9ZTnvoPfPJPM+o77l54bscp4AXMNfzoMhz7gVBUDAOeGZ5xJ217Rzy 32 | v152p6V2wpg4NqUmXbu+ySx7bfpJvDLXMfOpH8ZJZAXfhui3IfpvH6I3D78DK9ghmAMPAAA= 33 | headers: 34 | connection: [Keep-Alive] 35 | content-encoding: [gzip] 36 | content-type: [application/json] 37 | date: ['Thu, 23 Jul 2015 20:53:31 GMT'] 38 | keep-alive: ['timeout=5, max=100'] 39 | ms-author-via: [DAV] 40 | server: [Apache] 41 | set-cookie: ['session=s%3AJdnEQAQZ-Rl_QZHhytLzng-cyjr5HKDl.1s1WN%2BJMg4hLLZUqYZAx%2FD%2FOcqgjs8l%2BRLPMzBcFMWI; 42 | Path=/; Expires=Fri, 24 Jul 2015 20:53:31 GMT; HttpOnly; Secure'] 43 | vary: [Accept-Encoding] 44 | x-xcsapiversion: ['3'] 45 | x-xcsresponse-status-title: [OK] 46 | x-xcsresultslist: ['true'] 47 | status: {code: 200, message: OK} 48 | version: 1 49 | -------------------------------------------------------------------------------- /tests/fixtures/vcr_cassettes/integration.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Connection: [keep-alive] 8 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 9 | method: GET 10 | uri: https://127.0.0.1/xcode/api/integrations/88c98ee21f3895749ec3888b93009ea3 11 | response: 12 | body: 13 | string: !!binary | 14 | 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 52 | headers: 53 | connection: [Keep-Alive] 54 | content-encoding: [gzip] 55 | content-type: [application/json] 56 | date: ['Thu, 23 Jul 2015 21:59:08 GMT'] 57 | keep-alive: ['timeout=5, max=100'] 58 | ms-author-via: [DAV] 59 | server: [Apache] 60 | set-cookie: ['session=s%3AsiGtgnr-zaKeM19TmkOTCdjFRui90jQ6.blflXSye5bcKXoAyfyGCFnrvDNH2TBAW8N0%2BAGfdj3w; 61 | Path=/; Expires=Fri, 24 Jul 2015 21:59:08 GMT; HttpOnly; Secure'] 62 | vary: [Accept-Encoding] 63 | x-xcsapiversion: ['3'] 64 | x-xcsresponse-status-title: [OK] 65 | status: {code: 200, message: OK} 66 | version: 1 67 | -------------------------------------------------------------------------------- /tests/fixtures/vcr_cassettes/integration_cancel.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Authorization: [Basic YWE6cGFzcw==] 8 | Connection: [keep-alive] 9 | Content-Length: ['0'] 10 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 11 | method: POST 12 | uri: https://127.0.0.1/xcode/api/bots/88c98ee21f3895749ec3888b930017be/integrations 13 | response: 14 | body: 15 | string: !!binary | 16 | H4sIAAAAAAAAA+1X247bNhD9lULPKy9JUbd908VuFw2axXqTFg2ChUyNbDayqFDUNm6w/96h5Fuc 17 | beMN+tAWeTJMzuXMzNHM8KNzL0vnyokiEUcAjFZeFPshj0F4URQtYo+CH5Wec+Hca3hASc+tgoJS 18 | j9LII1BQ3/NAVBFQsqBVWcbAUXahjHP18Yu2CaHhAg62WUhcGgWVH4kyDKD0wjgmvCAFlL7HwI9D 19 | P0bppVZ9a+03xRpQLQnSGfNmzI2ns9zlMz92Yz8I3CzIeRizgGdx6jxeOEI1lVz2ujBSNVZ/0cva 20 | zLRaZzUUeEIunBa0VKUUc7GCsq/hujGgH4p6uBSqhEw9gC6WcKOhAg2NQARs0KuUXnd30JlEjA6q 21 | ou7gwjFaLpegO+fqzduDYNIU9eYPOJHt0O0afhrjsrYwkx2GDB9apU13o1XZiwFyosVKPqCY0b11 22 | grKyWebwIAVc51tn5fB33oKQlRT7uCsMewT00WnrwlhAh3rxkGOxIh4RCmEUxMDYwuPCYxEhHvMi 23 | hFPKDvU2W5zy5fxQQ+4WglHCOWVVQCtS+sxjQRkswspfQEnA1ruT6x79Kn1dQmMQG2hUFWo9Kdq2 24 | hskO1ES2K9XAXhxV5VkayibNlrf8qV8vBlnqJdwj/hIvsILdkAonnhDLjDEhd5sWhjoXNrcGhOk1 25 | bA8f99k8QB6SjNq/A7yrNzvG5MVmMLKWTY9VSSq0/IPq9Z2yXFoi+0Yn3VZ+dOBdOCsUelnthAZ8 26 | 5IgvY713fDmpuv0ZdPYxdGhNIFsbo1Wd1j20WjbDV5m/vpsfX/6s9LuuLQTspV6okSvdj7CxGoRP 27 | iRelaYiffRrmOc9oPPP9eMpnWULSdDoNcxJm/CnrqS4asTokbTDprIsO04KleFr8ZXtwzyj/XOyv 28 | MdsEjD6eNu08Pp5j7kbLdaE3t7BWBm6hVZ1E/m1Gy2fn4xxP9gRrmKl2c/BzUoGzIJ9iTXqzslkf 29 | Lc2NZd5Swj9R1Oc6/5uiFJ0UTyLdnFmpo/zNDSo+Pz5ylp9TCnPKGUsJd9NkOnV57OVuGsWJG5Iw 30 | nWYJzRljzyXATWFWz8c/tIF7OykunbNCsX17DOJoxHxZ7fWubTLinUdIbMrYs2xUdwpn12/YUj/1 31 | O/lgZ2qLV2chOGHZjsxvvoahr25fjFhWxrTd1eXlUppVv5jgTLlUhdbFQl7uEzvBy69BuJlvsNGt 32 | R0eHaVWayS828MnYpcVobfL9Vzo5pebZ1Hl8a78xDe97qaF71eLgKWG/jDSfriHfpcrCM8OEoTiJ 33 | D5PqXqjeLkp47oU4KJW4H8WGTRB1ZLO5zu2GxwlPM7tC1DgBbnGgWkb9d8bTzvUO+SheLlgUBmUV 34 | BQL7QBjzIAwBl2GB+2tccQF4R0MSnDXvfPK/HneftdEoCJMkJm4e4crOs1nqxtRnbhhkfhZxluAD 35 | 41sb/dZG//1ttMMFvi63T8ltCxW9xjeimRvAByvu8k2JFEVwzfZVMrTL9z30UObDw8BhhPouCV3G 36 | 7xi7IvSKkgkC+NW+mnohoOvuO6OheDds+Ued9qgdH3fcLKKzfMqcxz8BjVmN1esPAAA= 37 | headers: 38 | connection: [Keep-Alive] 39 | content-encoding: [gzip] 40 | content-type: [application/json] 41 | date: ['Fri, 24 Jul 2015 22:01:10 GMT'] 42 | keep-alive: ['timeout=5, max=100'] 43 | location: ['https://127.0.0.1:20343/integration/88c98ee21f3895749ec3888b931e58d3'] 44 | ms-author-via: [DAV] 45 | server: [Apache] 46 | set-cookie: ['session=s%3A3PP19WPhj50rltUO5MZ29nPYcNbz6V8c.Jv5Je%2BdzmmY69%2F7DdHMdOiahZxMsiJfd7IxDhwW73xM; 47 | Path=/; Expires=Sat, 25 Jul 2015 22:01:10 GMT; HttpOnly; Secure'] 48 | vary: ['X-HTTP-Method-Override,Accept-Encoding'] 49 | x-xcsapiversion: ['3'] 50 | x-xcsresponse-status-title: [Created] 51 | status: {code: 201, message: Created} 52 | - request: 53 | body: null 54 | headers: 55 | Accept: ['*/*'] 56 | Accept-Encoding: ['gzip, deflate'] 57 | Authorization: [Basic YWE6cGFzcw==] 58 | Connection: [keep-alive] 59 | Content-Length: ['0'] 60 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 61 | method: POST 62 | uri: https://127.0.0.1/xcode/api/integrations/88c98ee21f3895749ec3888b931e58d3/cancel 63 | response: 64 | body: {string: !!python/unicode ''} 65 | headers: 66 | connection: [Keep-Alive] 67 | content-length: ['0'] 68 | date: ['Fri, 24 Jul 2015 22:01:11 GMT'] 69 | etag: [W/"a-b541a50d"] 70 | keep-alive: ['timeout=5, max=100'] 71 | ms-author-via: [DAV] 72 | server: [Apache] 73 | set-cookie: ['session=s%3AWZxay2LeSHihKeUWkq9LHANkmDSRsU7Z.5Gliudr8o3u1z3U84QJtXJBUBg%2FK%2BowD3y5pvO3CXlM; 74 | Path=/; Expires=Sat, 25 Jul 2015 22:01:11 GMT; HttpOnly; Secure'] 75 | vary: [X-HTTP-Method-Override] 76 | x-xcsapiversion: ['3'] 77 | x-xcsresponse-status-title: [No Content] 78 | status: {code: 204, message: No Content} 79 | version: 1 80 | -------------------------------------------------------------------------------- /tests/fixtures/vcr_cassettes/integration_new.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Connection: [keep-alive] 8 | Content-Length: ['0'] 9 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 10 | method: POST 11 | uri: https://127.0.0.1/xcode/api/bots/88c98ee21f3895749ec3888b930017be/integrations 12 | response: 13 | body: 14 | string: !!binary | 15 | H4sIAAAAAAAAA+1X227bRhD9lYLPprxcLm9640VqjQaxYTlp0SAwlsuhtLXEZZakGzbwv3eW1C2q 16 | 28hBH9oiTwS5czkzczgz+8m6l4U1tcJQRCEAdUo3jLyARSDcMAzzyCWUlIxYF9a9hkeUdG0aiNIp 17 | /DAPclKEJRMR93weOG7BeRi5gLK5aq3ppy/bJk6Qw8G249ucBW5Og9zLWcRK13OB5g73csejvidC 18 | F4WXWnW1MV/xDaBW7Cdz6s6pHc3mmc3mXmRHnu/bqZ+xIKI+S6PEerqwhKpKuew0b6WqjH7eyXU7 19 | 12qTroHjF3Jh1aClKqRYiBUU3Rquqhb0I18Ph0IVkKpH0HwJNxpK0FAJREAHvVLpTXMHTRuL0UHJ 20 | 1w1cWK2WyyXoxpq+e38QjCu+7n+HE9kG3W7g9RiXsYWJbDBk+Fgr3TY3WhWdGCDHWqzkI4q1ujNO 21 | UFZWywwepYCrbOusGF4XNQhZSrGPu8SwR0CfrHrNWwPoUC4WMKxVyELiQBD6EVCau0y4NCTEpW6I 22 | cArZoF6/xSmvF4cSMpsL6hDGHFr6TkkKj7rUL/w8KL0cCgKm3I3cdOhX6asCqhaxgUZVoTYTXtdr 23 | mOxATWS9UhXsxVFVnqWhTNJMeYvX3SYfZB03Zi7xlniAFWyGVFjRhBhmjAm562sY6sxNblsQbadh 24 | +/Fpn80D5CHJqP0bwMO63zEm4/1gZCOrDqsSl2j5B9XpO2W4tET2jU6arfzowL2wVih0Xe6EBnzk 25 | iC9jvXd8Oam6eQw6+xgatCaQrVWr1TpZd1BrWQ0/Zfb2bnF8+JPSD03NBeylXqmRK82P0BsNwmbE 26 | DZMEf3EnCbKMpU4097xoxuZpTJJkNgsyEqTsOeuJ5pVYHZI2mLQ2vMG0YCmeF7+uD+6pw/4s9teY 27 | TQJGH8+btp6ezjF3o+WG6/4WNqqFW6hVI5F//Wj57Hyc48l8wRqmqu4Pfk4qcBbkU6xx165M1kdL 28 | i9YwbynhnyjqS53/TVF4I8WzSPszK3WUv0WLii+Pj5zl55TCzGGUJoTZSTyb2SxyMzsJo9gOSJDM 29 | 0tjJKKUvJcANb1cvxz+0gXszKS6ts0IxfXsM4mjEfFnt7a5tUuKeR0hsytizTFR3CmfXr9hSP/c7 30 | +Whmao1HZyE4YdmOzO++hqFvbl+NWFZtWzfTy8ulbFddPsGZcqm41jyXl/vETvDwaxD2ix4b3WZ0 31 | dJhWRTv52QQ+Gbu0GK1Nvv9KJ6fUPJs6T+/NP6bhQyc1NG9qHDwF7JeR6vM15LtEGXjtMGEcnMSH 32 | SXUvVGcWJWsa4phU4n4UGtZA1JBVf5XhK2WEJalZINbY/29xnBo+/XeG0871DvkoXuQ0DPyiDH2B 33 | XSCImB8EUBaFCKIowu0Y8MwJiH/WtPPI/3rYnabaS7xZNg+pHWZ+iqt7GtmxR3CJj9MkiiN/7rDg 34 | WxP91kT//U20wfV9XWwvktsGKjqNN8R20QJeV3GTrwqkKIKrtncSbJYfOuigyIZLgUWJ49kksKl7 35 | R+nUCabEnTgs/MXcmDohoGnum1YDfxg2/KM+e9SKj/ttlDjJPMa77x/dKgi05g8AAA== 36 | headers: 37 | connection: [Keep-Alive] 38 | content-encoding: [gzip] 39 | content-type: [application/json] 40 | date: ['Thu, 23 Jul 2015 22:17:03 GMT'] 41 | keep-alive: ['timeout=5, max=100'] 42 | location: ['https://127.0.0.1:20343/integration/88c98ee21f3895749ec3888b93020f40'] 43 | ms-author-via: [DAV] 44 | server: [Apache] 45 | set-cookie: ['session=s%3AHAfj-YlSBVmdYggC3-lIu5i0AeUUCh97.c2LPVIFUhN1zD9y4xhaI6fVQkCYhisBHQ1usPazstFQ; 46 | Path=/; Expires=Fri, 24 Jul 2015 22:17:03 GMT; HttpOnly; Secure'] 47 | vary: ['X-HTTP-Method-Override,Accept-Encoding'] 48 | x-xcsapiversion: ['3'] 49 | x-xcsresponse-status-title: [Created] 50 | status: {code: 201, message: Created} 51 | version: 1 52 | -------------------------------------------------------------------------------- /tests/fixtures/vcr_cassettes/integration_running.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Authorization: [Basic YWE6cGFzcw==] 8 | Connection: [keep-alive] 9 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 10 | method: GET 11 | uri: https://127.0.0.1/xcode/api/integrations/running 12 | response: 13 | body: 14 | string: !!binary | 15 | H4sIAAAAAAAAA4WNSw4CIRAF7/LWLPhopuEaLo0x0tMTjQqGgRXh7nKD2VaqUh2cW6oIRqHI3j51 16 | R7h2xDxZx/21IoCIPYlYszny5+XkhR0RRe+0NksUDHVo2ofI6qCQ2jdKQbB6LrmVIqleqvxmzU/h 17 | d24V4zb+WVT5pZkAAAA= 18 | headers: 19 | connection: [Keep-Alive] 20 | content-encoding: [gzip] 21 | content-type: [application/json] 22 | date: ['Sat, 25 Jul 2015 23:50:28 GMT'] 23 | keep-alive: ['timeout=5, max=100'] 24 | ms-author-via: [DAV] 25 | server: [Apache] 26 | set-cookie: ['session=s%3AvzhZqVL3LwtFQ0Wfsz3w1la3MaBfqy5H.H%2FrUwXnJIpaYyngHpRSYcaKC8Spoz8%2Fk4H3r6ppmGTY; 27 | Path=/; Expires=Sun, 26 Jul 2015 23:50:28 GMT; HttpOnly; Secure'] 28 | vary: [Accept-Encoding] 29 | x-xcsapiversion: ['3'] 30 | x-xcsresponse-status-title: [OK] 31 | x-xcsresultslist: ['true'] 32 | status: {code: 200, message: OK} 33 | - request: 34 | body: null 35 | headers: 36 | Accept: ['*/*'] 37 | Accept-Encoding: ['gzip, deflate'] 38 | Authorization: [Basic YWE6cGFzcw==] 39 | Connection: [keep-alive] 40 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 41 | method: GET 42 | uri: https://127.0.0.1/xcode/api/integrations/88c98ee21f3895749ec3888b932aeed3 43 | response: 44 | body: 45 | string: !!binary | 46 | H4sIAAAAAAAAA+1X227bRhD9lYLPprzcXV6Wb7xIrdEgMSwlLRIEBrkcSqwlklkunaiB/72zpC6O 47 | 6jZy0Ie2CCBAEncuZ2YOZ2Y/W7dVYYVWEEgRAFCnZIFwfS5AsiAIcsFoBlAw68K6VXCPkp7NKOPg 48 | SFe4ueMEhZM7sigLVgKRAQQFRdm80Vb4+au2CXH8HI62mXBtRnxPMlFkvpflZSGCzGWEM3ACwlzP 49 | LVF6qZq+NfbrbAOoFnnxjLIZtcV0ltp85gpbuJ5nJ17KfUE9nojYeriwZFOX1bJXma6a2ujnfbXW 50 | M9VskjVk+IRcWC2oqikqOZcrKPo1XNUa1H22Hg5lU0DS3IPKlnCtoAQFtUQEdNArG7XpFtDpSI4O 51 | ymzdwYWlVbVcguqs8N37o2BUZ+vt73Ai26HbDbwc4zK2MJMdhgyf2kbp7lo1RS8HyJGSq+oexbTq 52 | jROUreplCveVhKt056wY/s5bkFVZyUPcJYY9AvpstetMG0DHenGfY7ECHhAH/MATQGnOuGQ0IARr 53 | HyCcoupQb7vDWb2aH2vI7UxSh3Du0NJzSlK4lFGv8HK/dHMoCJh6d9WmR7+Nuiqg1ogNFKrKZjPJ 54 | 2nYNkz2oSdWumhoO4qhanaXRmKSZ8hYv+00+yDos4oy4SzzACnZDKiwxIYYZY0IW2xaGOmcmtxqk 55 | 7hXsHj4csnmEPCQZtT8C3K23e8ak2XYwsqnqHqsSlWj5p6ZXi8ZwaYnsG510O/nRAbuwVij0qtwL 56 | DfjII76M9d7z5aTq5mvQOcTQoTWJbK21atbxuodWVfXwVqZvFvPHh7806q5rMwkHqRfNyJXuZ9ga 57 | DcKnhAVx7DvMif005YkjZq4rpnyWRCSOp1M/JX7Cn7Ieq6yWq2PSBpPWJuswLViKp8VftUf31OF/ 58 | FvtrzCYBo4+nTVsPD+eYu1bVJlPbG9g0Gm6gbboK+bcdLZ+dj3M8mSdYw6Rpt0c/JxU4C/Ip1qjX 59 | K5P10dJcG+YtK/gnivpc539TlKyr5JNIt2dW6lH+5hoVnx8fOcvPKYW5wymNCbfjaDq1uWCpHQci 60 | sn3ix9MkclJK6XMJcJ3p1fPxD23g1kyKS+usUEzfHoN4NGK+rvZm3zYpYecREpsy9iwT1aLB2fUb 61 | ttQv/U4+mZna4tFZCE5Ytifzu29h6OubFyOWldZtF15eLiu96vMJzpTLJlMqy6vLQ2InePgtCLfz 62 | LTa6zejoOK0KPfnVBD4Zu7QcrU1+/EYnp9Q8mzoP7807puBDXynoXrc4eAo4LCP1l2vID3Fj4Olh 63 | wjg4iY+T6lY2vVmUDDXwpGjk7Sg2bIKoU9XbqxT/Uk54nJgVYo0T4AYHqmHUf2c87V3vkY/iBXMZ 64 | z4RLJOW5kJwCrrhO4cscfwaB4F4gJATOWfPOJf/rcXeaaj8VsyiZ+Tafpri8c07tIPATO/WjmFEa 65 | cc9PvrfR7230399GO1zg18XuKrlrobJXeEfUcw2tQb4Cedf0Bl29u5YM/fJDDz0U6XAzsPCJaxPf 66 | pu6CMuwGIfUmHnHfmmtTLyV03W2nFWR3w5r/qNU+6sePWy6nInHjYH8fmuN1Fm8xM3xVQO2arsVm 67 | oReHXhAmQejEYTQNIxGKNJxOQ4eG3A2nUUjScBaZD2chd8KIhghPmHdTZ8vdfbPTmdJQLKrN05H4 68 | E9cXb62HPwCL5V02dxAAAA== 69 | headers: 70 | connection: [Keep-Alive] 71 | content-encoding: [gzip] 72 | content-type: [application/json] 73 | date: ['Sat, 25 Jul 2015 23:50:28 GMT'] 74 | keep-alive: ['timeout=5, max=100'] 75 | ms-author-via: [DAV] 76 | server: [Apache] 77 | set-cookie: ['session=s%3AsILVj7zX4NyxlNxRpTJNDsiiG6qac8a3.CImYJT16vow%2BRIpmOPT%2FfbHgz9835HR%2FV1%2Bu0U7Vu3Y; 78 | Path=/; Expires=Sun, 26 Jul 2015 23:50:28 GMT; HttpOnly; Secure'] 79 | vary: [Accept-Encoding] 80 | x-xcsapiversion: ['3'] 81 | x-xcsresponse-status-title: [OK] 82 | status: {code: 200, message: OK} 83 | version: 1 84 | -------------------------------------------------------------------------------- /tests/fixtures/vcr_cassettes/integrations.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Connection: [keep-alive] 8 | User-Agent: [python-requests/2.7.0 CPython/2.7.9 Darwin/15.0.0] 9 | method: GET 10 | uri: https://127.0.0.1/xcode/api/bots/88c98ee21f3895749ec3888b930017be/integrations 11 | response: 12 | body: 13 | string: !!binary | 14 | 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 65 | headers: 66 | connection: [Keep-Alive] 67 | content-encoding: [gzip] 68 | content-type: [application/json] 69 | date: ['Thu, 23 Jul 2015 21:49:00 GMT'] 70 | keep-alive: ['timeout=5, max=100'] 71 | ms-author-via: [DAV] 72 | server: [Apache] 73 | set-cookie: ['session=s%3AuxwNavFTKGqpQASmrtYuTarbwIxStsoi.az9T76bhU47V2OK%2FSTx%2BbRL9fUV4vSoR3g%2BOfxGad5o; 74 | Path=/; Expires=Fri, 24 Jul 2015 21:49:00 GMT; HttpOnly; Secure'] 75 | vary: [Accept-Encoding] 76 | x-xcsapiversion: ['3'] 77 | x-xcsresponse-status-title: [OK] 78 | x-xcsresultslist: ['true'] 79 | status: {code: 200, message: OK} 80 | version: 1 81 | -------------------------------------------------------------------------------- /tests/integrations_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import vcr 3 | from xserverpy.lib.integrations import Integrations 4 | from xserverpy.lib.xcode_server import XcodeServer 5 | from xserverpy.lib.user import User 6 | from xserverpy.utils.settings import Settings 7 | 8 | 9 | class TestIntegrations(unittest.TestCase): 10 | 11 | def setUp(self): 12 | user = User(user="aa", password="pass") 13 | server = XcodeServer(host="https://127.0.0.1") 14 | self.settings = Settings(server, user) 15 | 16 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/integrations.yaml') 17 | def test_can_request_integrations(self): 18 | b = Integrations(self.settings, "88c98ee21f3895749ec3888b930017be") 19 | assert len(b.get_all()) == 4 20 | 21 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/integration.yaml') 22 | def test_can_request_1_integration(self): 23 | b = Integrations(self.settings, "88c98ee21f3895749ec3888b930017be") 24 | self.assertEqual(b.get_item(item_id="88c98ee21f3895749ec3888b93009ea3").result, 25 | "succeeded") 26 | 27 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/integration_new.yaml') 28 | def test_can_request_new_integration(self): 29 | b = Integrations(self.settings, "88c98ee21f3895749ec3888b930017be") 30 | self.assertIsNotNone(b.integrate()) 31 | 32 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/integration_cancel.yaml') 33 | def test_can_cancel_integration(self): 34 | i = Integrations(self.settings, "88c98ee21f3895749ec3888b930017be") 35 | integration = i.integrate() 36 | self.assertIsNotNone(integration) 37 | result = i.cancel_integration(integration.id) 38 | self.assertTrue(result) 39 | 40 | @vcr.use_cassette('tests/fixtures/vcr_cassettes/integration_running.yaml') 41 | def test_can_request_running_integration(self): 42 | b = Integrations(self.settings) 43 | running = b.get_running_integration() 44 | self.assertIsNotNone(running) 45 | self.assertEqual(running[0].bot.name, "Testbots Bot") 46 | -------------------------------------------------------------------------------- /tests/settings_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | from xserverpy.utils.settings import Settings, EmptySettings 4 | import glob 5 | import shutil 6 | from mock import MagicMock 7 | 8 | 9 | class SettingsTests(unittest.TestCase): 10 | 11 | def setUp(self): 12 | self.f = ".xserverpy.test" 13 | try: 14 | os.remove(".test_xserverpy") 15 | except: pass 16 | 17 | try: 18 | os.remove(os.path.expanduser("~/.test_xserverpy")) 19 | except: pass 20 | 21 | def test_can_save_to_local_file(self): 22 | 23 | server = MagicMock() 24 | server.__dict__ = {"host": "123123", "port": "8080"} 25 | 26 | user = MagicMock() 27 | user.__dict__ = {"user": "testuser", "password": "password"} 28 | 29 | self.assertEqual(os.path.exists(".test_xserverpy"), False) 30 | settings = Settings(server, user) 31 | settings.store(False, "test_xserverpy") 32 | self.assertEqual(os.path.exists(".test_xserverpy"), True) 33 | 34 | def test_can_save_to_global_file(self): 35 | 36 | server = MagicMock() 37 | server.__dict__ = {"host": "123123", "port": "8080"} 38 | 39 | user = MagicMock() 40 | user.__dict__ = {"user": "testuser", "password": "password"} 41 | 42 | path = os.path.expanduser("~/.test_xserverpy") 43 | self.assertEqual(os.path.exists(path), False) 44 | settings = Settings(server, user) 45 | settings.store(True, "test_xserverpy") 46 | self.assertEqual(os.path.exists(path), True) 47 | 48 | def test_can_load_from_global_file(self): 49 | self.dummy_store(True) 50 | path = os.path.expanduser("~/.test_xserverpy") 51 | self.assertEqual(os.path.exists(path), True) 52 | self.assertEqual(os.path.exists(".test_xserverpy"), False) 53 | 54 | settings = Settings.load("test_xserverpy") 55 | self.assertEqual(settings.server.host, "http://1.2.2.2/xcode/api") 56 | self.assertEqual(settings.server.port, "8080") 57 | self.assertEqual(settings.user.user, "testuser") 58 | self.assertEqual(settings.user.password, "password") 59 | 60 | def test_can_load_from_local_file(self): 61 | self.dummy_store(False) 62 | path = os.path.expanduser("~/.test_xserverpy") 63 | self.assertEqual(os.path.exists(path), False) 64 | self.assertEqual(os.path.exists(".test_xserverpy"), True) 65 | 66 | settings = Settings.load("test_xserverpy") 67 | self.assertEqual(settings.server.host, "http://1.2.2.2/xcode/api") 68 | self.assertEqual(settings.server.port, "8080") 69 | self.assertEqual(settings.user.user, "testuser") 70 | self.assertEqual(settings.user.password, "password") 71 | 72 | def test_returns_none_if_no_file(self): 73 | path = os.path.expanduser("~/.test_xserverpy") 74 | self.assertEqual(os.path.exists(path), False) 75 | self.assertEqual(os.path.exists(".test_xserverpy"), False) 76 | 77 | settings = Settings.load("test_xserverpy") 78 | self.assertIsInstance(settings, EmptySettings) 79 | 80 | def test_can_updated_an_empty_with_user_and_server(self): 81 | settings = EmptySettings() 82 | self.assertIsNone(settings.server) 83 | self.assertIsNone(settings.user) 84 | settings.update(MagicMock(), MagicMock()) 85 | self.assertIsNotNone(settings.server) 86 | self.assertIsNotNone(settings.user) 87 | 88 | def test_validates_correctly(self): 89 | server = MagicMock() 90 | server.__dict__ = {"host": None, "port": "80820"} 91 | 92 | user = MagicMock() 93 | user.__dict__ = {"user": "testuser2", "password": "2222"} 94 | settings = Settings(server, user) 95 | 96 | with self.assertRaises(RuntimeError): 97 | settings.validate() 98 | 99 | server = MagicMock() 100 | server.__dict__ = {"host": "http://1.2.2.2/xcode/api", "port": None} 101 | settings.server = server 102 | with self.assertRaises(RuntimeError): 103 | settings.validate() 104 | 105 | def test_can_updated_a_non_empty_with_user_and_server(self): 106 | self.dummy_store(False) 107 | path = os.path.expanduser("~/.test_xserverpy") 108 | self.assertEqual(os.path.exists(path), False) 109 | self.assertEqual(os.path.exists(".test_xserverpy"), True) 110 | 111 | settings = Settings.load("test_xserverpy") 112 | self.assertEqual(settings.server.host, "http://1.2.2.2/xcode/api") 113 | self.assertEqual(settings.server.port, "8080") 114 | self.assertEqual(settings.user.user, "testuser") 115 | self.assertEqual(settings.user.password, "password") 116 | 117 | server = MagicMock() 118 | server.__dict__ = {"host": "http://1.2.2.4/xcode/api", "port": "80820"} 119 | 120 | user = MagicMock() 121 | user.__dict__ = {"user": "testuser2", "password": "2222"} 122 | settings.update(server, user) 123 | self.assertEqual(settings.server.host, "http://1.2.2.4/xcode/api") 124 | self.assertEqual(settings.server.port, "80820") 125 | self.assertEqual(settings.user.user, "testuser2") 126 | self.assertEqual(settings.user.password, "2222") 127 | 128 | # If value is none, dont overwrite it 129 | server = MagicMock() 130 | server.__dict__ = {"host": None, "port": None} 131 | 132 | user = MagicMock() 133 | user.__dict__ = {"user": "testuser2", "password": "2222"} 134 | settings.update(server, user) 135 | self.assertEqual(settings.server.host, "http://1.2.2.4/xcode/api") 136 | self.assertEqual(settings.server.port, "80820") 137 | self.assertEqual(settings.user.user, "testuser2") 138 | self.assertEqual(settings.user.password, "2222") 139 | 140 | def dummy_store(self, is_global): 141 | server = MagicMock() 142 | server.__dict__ = {"host": "http://1.2.2.2/xcode/api", "port": "8080"} 143 | 144 | user = MagicMock() 145 | user.__dict__ = {"user": "testuser", "password": "password"} 146 | settings = Settings(server, user) 147 | settings.store(is_global, "test_xserverpy") 148 | -------------------------------------------------------------------------------- /tests/xcode_server_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | test_xserverpy 6 | ---------------------------------- 7 | 8 | Tests for `xserverpy` module. 9 | """ 10 | 11 | import unittest 12 | 13 | from xserverpy.lib.xcode_server import XcodeServer 14 | 15 | 16 | class TestXserverpy(unittest.TestCase): 17 | 18 | def test_throws_error_if_corrupted_params(self): 19 | with self.assertRaises(RuntimeError): 20 | XcodeServer(host="host") 21 | 22 | def test_succeeds_if_all_items_are_correct(self): 23 | try: 24 | XcodeServer(host="https://123.123.123.3") 25 | except: 26 | self.fail("") 27 | 28 | if __name__ == '__main__': 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26, py27, py33, py34 3 | 4 | [testenv] 5 | setenv = 6 | PYTHONPATH = {toxinidir}:{toxinidir}/xserverpy 7 | commands = python setup.py test 8 | deps = 9 | -r{toxinidir}/requirements.txt 10 | -------------------------------------------------------------------------------- /xserverpy.py: -------------------------------------------------------------------------------- 1 | from xserverpy import xserverpy 2 | 3 | xserverpy.start() 4 | -------------------------------------------------------------------------------- /xserverpy.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "build_systems": 3 | [ 4 | { 5 | "name": "Build Project", 6 | "shell_cmd": "make test", 7 | "working_dir": "${project_path:${folder}}" 8 | } 9 | ], 10 | "folders": 11 | [ 12 | { 13 | "path": "." 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /xserverpy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = 'Omar Abdelhafith' 4 | __email__ = 'o.arrabi@me.com' 5 | __version__ = '0.1.0' 6 | -------------------------------------------------------------------------------- /xserverpy/display/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /xserverpy/display/bots_printer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from xserverpy.utils.config import * 4 | from tabulate import tabulate 5 | 6 | 7 | class BotsPrinter(): 8 | 9 | def __init__(self, bots): 10 | self.bots = bots 11 | 12 | def print_items(self): 13 | # print self.bots 14 | printed = map(self.prepare, self.bots) 15 | print tabulate(printed, headers=['Bot name', 'ID', 'Number of integrations']) 16 | 17 | @staticmethod 18 | def prepare(bot): 19 | return [bot.name, bot.id, bot.integration_counter - 1] 20 | -------------------------------------------------------------------------------- /xserverpy/display/integrations_printer.py: -------------------------------------------------------------------------------- 1 | from xserverpy.utils.config import * 2 | from tabulate import tabulate 3 | 4 | 5 | class IntegrationsPrinter(): 6 | 7 | @classmethod 8 | def print_integrations(cls, integrations): 9 | printed = map(cls.prepare, integrations) 10 | 11 | if len(integrations) == 0: 12 | info("Selected bot has 0 integrations") 13 | return 14 | 15 | info("\nListing all integrations for bot '%s'" % integrations[0].bot.name) 16 | print tabulate(printed, headers=['Bot', 'Number', 'ID', 'Step', 'Result', 'Date']) 17 | 18 | @classmethod 19 | def print_running(cls, integrations): 20 | if integrations is None: 21 | return 22 | 23 | info("%d Integrations running currently" % len(integrations)) 24 | 25 | if len(integrations) == 0: 26 | return 27 | 28 | print("") 29 | printed = map(cls.prepare, integrations) 30 | print tabulate(printed, headers=['Bot', 'Number', 'ID', 'Step', 'Result', 'Date']) 31 | 32 | @classmethod 33 | def prepare(cls, integration): 34 | return [integration.bot.name, integration.number, integration.id, 35 | integration.step, integration.result, 36 | integration.date_string()] 37 | -------------------------------------------------------------------------------- /xserverpy/lib/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = 'Omar Abdelhafith' 4 | __email__ = 'o.arrabi@me.com' 5 | __version__ = '0.1.0' 6 | -------------------------------------------------------------------------------- /xserverpy/lib/base_service.py: -------------------------------------------------------------------------------- 1 | import urlparse 2 | import requests 3 | import json 4 | 5 | 6 | class BaseService(): 7 | 8 | def __init__(self, settings): 9 | self.server = settings.server 10 | urlparts = urlparse.urlparse(self.server.host) 11 | self.user = settings.user 12 | self.url = "%s://%s:%s/xcode/api/" % (urlparts.scheme, urlparts.netloc, settings.server.port) 13 | 14 | def get_all(self): 15 | result = self.perform_request(self.endpoint(), "get") 16 | return self.parse_response(result[0]) 17 | 18 | def post(self): 19 | result = self.perform_request(self.endpoint(), "post") 20 | return self.item_class().from_json(json.loads(result[0])) 21 | 22 | def get_item(self, item_id=None): 23 | if not item_id: 24 | raise RuntimeError("item_id is reuired") 25 | 26 | result = self.perform_request(self.singe_item_endpoint(item_id), "get") 27 | if not result: return None 28 | 29 | return self.item_class().from_json(json.loads(result[0])) 30 | 31 | def perform_request(self, path_url, http_method): 32 | try: 33 | requests.packages.urllib3.disable_warnings() 34 | method = getattr(requests, http_method, None) 35 | r = method( 36 | url=self.url + path_url, 37 | auth=self.basic_auth(), 38 | verify=False 39 | ) 40 | 41 | if r.status_code in (200, 201, 204): 42 | return r.content, r.status_code 43 | if r.status_code == 409: 44 | raise RuntimeError("Conflict happened, please retry later") 45 | if r.status_code in (401, 403): 46 | if self.basic_auth(): 47 | raise RuntimeError("User name or password provided are not correct") 48 | else: 49 | raise RuntimeError("Xcode server requires user and password, " 50 | "use --user USER and --pass PASSWORD") 51 | except Exception as e: 52 | raise e 53 | 54 | raise RuntimeError("Unknown error occurred") 55 | 56 | def basic_auth(self): 57 | return self.user.basic_auth() 58 | 59 | def parse_response(self, response): 60 | parsed = json.loads(response) 61 | results = parsed["results"] 62 | items = map(lambda x: self.item_class().from_json(x), results) 63 | return items 64 | 65 | # Subclass 66 | def endpoint(self): 67 | raise RuntimeError("Must be implemented") 68 | 69 | def singe_item_endpoint(self, item_id): 70 | raise RuntimeError("Must be implemented") 71 | 72 | def item_class(self): 73 | raise RuntimeError("Must be implemented") 74 | -------------------------------------------------------------------------------- /xserverpy/lib/bot.py: -------------------------------------------------------------------------------- 1 | class Bot(): 2 | 3 | @staticmethod 4 | def from_json(json): 5 | if json: 6 | return Bot(name=json.get("name", ""), 7 | id=json["_id"], 8 | integration_counter=json.get("integration_counter", "")) 9 | else: 10 | return EmptyBot() 11 | 12 | def __init__(self, **args): 13 | self.name = args["name"] 14 | self.id = args["id"] 15 | self.integration_counter = args["integration_counter"] 16 | 17 | def __repr__(self): 18 | return self.__dict__.__str__() 19 | 20 | 21 | class EmptyBot(): 22 | 23 | def __init__(self, **args): 24 | self.name = " - " 25 | self.id = " - " 26 | self.integration_counter = " - " 27 | -------------------------------------------------------------------------------- /xserverpy/lib/bots.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from bot import Bot 4 | from base_service import BaseService 5 | import itertools 6 | import sys 7 | 8 | 9 | class Bots(BaseService): 10 | 11 | def endpoint(self): 12 | return "bots" 13 | 14 | def singe_item_endpoint(self, bot_id): 15 | return "bots/%s" % bot_id 16 | 17 | def item_class(self): 18 | return Bot 19 | 20 | def get_named(self, bot_name): 21 | bots = self.get_all() 22 | try: 23 | return list(itertools.ifilter(lambda x: x.name == bot_name, bots))[0] 24 | except: 25 | return None 26 | -------------------------------------------------------------------------------- /xserverpy/lib/cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import argparse 3 | import sys 4 | 5 | def parse(args=None): 6 | parser = argparse.ArgumentParser( 7 | description='Command line interface for Xcode server.') 8 | sub_parsers = parser.add_subparsers(title="Commands", dest="sub") 9 | 10 | parser.add_argument('--version', help="Display version", action="count") 11 | 12 | add_bot_parser(sub_parsers) 13 | add_integrate_parser(sub_parsers) 14 | add_init_flags(sub_parsers) 15 | 16 | if has_pre_parse(parser): 17 | return pre_parse() 18 | 19 | parsed_args = parser.parse_args(args) 20 | 21 | parsed_args.bots = parsed_args.sub == "bots" 22 | parsed_args.integrations = parsed_args.sub == "list" 23 | parsed_args.integrate = parsed_args.sub == "new" 24 | parsed_args.cancel = parsed_args.sub == "cancel" 25 | parsed_args.init = parsed_args.sub == "init" 26 | parsed_args.running = parsed_args.sub == "running" 27 | 28 | return parsed_args 29 | 30 | 31 | def has_pre_parse(parser): 32 | if len(sys.argv) < 2: 33 | parser.print_help() 34 | sys.exit(0) 35 | 36 | return sys.argv[1] == "--version" 37 | 38 | 39 | def pre_parse(): 40 | is_version = sys.argv[1] == "--version" 41 | 42 | if is_version: 43 | parsed_args = argparse.Namespace() 44 | parsed_args.version = True 45 | return parsed_args 46 | 47 | return None 48 | 49 | 50 | def add_bot_parser(sub_parsers): 51 | help = "List Xcode server bots" 52 | parser = sub_parsers.add_parser("bots", 53 | help=help, 54 | description=help) 55 | add_common_flags(parser) 56 | 57 | 58 | def add_integrate_parser(sub_parsers): 59 | help = "Handle Xcode server integrations" 60 | integration_parser = sub_parsers.add_parser("integrations", 61 | help=help, 62 | description=help) 63 | 64 | integration_sub_parsers = integration_parser.add_subparsers(title="Commands", dest="sub") 65 | add_new_integration_parser(integration_sub_parsers) 66 | add_list_integrations_parser(integration_sub_parsers) 67 | add_cancel_integration_parser(integration_sub_parsers) 68 | add_running_integrations_parser(integration_sub_parsers) 69 | 70 | 71 | def add_new_integration_parser(integration_sub_parsers): 72 | help = "Integrate an Xcode bot" 73 | new_subparser = integration_sub_parsers.add_parser("new", 74 | help=help, 75 | description=help) 76 | 77 | add_common_flags(new_subparser) 78 | new_subparser.add_argument('--bot', help="Bot to integrate", 79 | action="store", required=True) 80 | new_subparser.add_argument('--interval', 81 | help="Interval to poll the server for updates, default .5s", 82 | action="store", type=int, default=.5) 83 | new_subparser.add_argument('--wait', help="Print integration steps progress", 84 | action="store_true", default=False) 85 | new_subparser.add_argument('--no-tty', help="Force non tty progress reporting", 86 | action="store_false", default=sys.stdout.isatty()) 87 | 88 | 89 | def add_list_integrations_parser(integration_sub_parsers): 90 | help = "List Xcode server integrations" 91 | list_parser = integration_sub_parsers.add_parser("list", 92 | help=help, 93 | description=help) 94 | add_common_flags(list_parser) 95 | list_parser.add_argument('--bot', help="Bot to list integrations for", 96 | action="store", default=None) 97 | 98 | 99 | def add_cancel_integration_parser(integration_sub_parsers): 100 | help = "Cancel a previos integration" 101 | cancel_subparser = integration_sub_parsers.add_parser("cancel", 102 | help=help, 103 | description=help) 104 | 105 | add_common_flags(cancel_subparser) 106 | cancel_subparser.add_argument('--id', help="Integration ID to cancel", 107 | action="store", required=True) 108 | 109 | 110 | def add_running_integrations_parser(integration_sub_parsers): 111 | help = "List running integrations" 112 | list_parser = integration_sub_parsers.add_parser("running", 113 | help=help, 114 | description=help) 115 | add_common_flags(list_parser) 116 | 117 | 118 | def add_common_flags(parser): 119 | parser.add_argument('--host', help="Xcode server host", 120 | action="store", default=None) 121 | parser.add_argument('--port', help="Xcode server host port, default 443", 122 | action="store", default=443) 123 | parser.add_argument('--user', help="Username to use for authentication", 124 | action="store", default=None) 125 | parser.add_argument('--password', help="Password to use for authentication", 126 | action="store", default=None) 127 | 128 | 129 | def add_init_flags(parser): 130 | help = "Setup user environment to be used with consequent commands" 131 | parser = parser.add_parser("init", 132 | help=help, 133 | description=help) 134 | 135 | parser.add_argument('--host', help="Xcode server host", 136 | action="store", required=True) 137 | parser.add_argument('--port', help="Xcode server host port, default 443", 138 | action="store", default=443) 139 | parser.add_argument('--user', help="Username to use for authentication", 140 | action="store", default=None) 141 | parser.add_argument('--password', help="Password to use for authentication", 142 | action="store", default=None) 143 | parser.add_argument('--local', help="Store configuration file in the local directory", 144 | action="store_true", default=False) 145 | -------------------------------------------------------------------------------- /xserverpy/lib/integration.py: -------------------------------------------------------------------------------- 1 | from bot import Bot 2 | import dateutil.parser 3 | import pytz 4 | from tzlocal import get_localzone 5 | 6 | 7 | class Integration(): 8 | 9 | @staticmethod 10 | def from_json(json): 11 | date = None 12 | if "queuedDate" in json: 13 | date = dateutil.parser.parse(json["queuedDate"]) 14 | 15 | return Integration(number=json["number"], 16 | id=json["_id"], 17 | result=json.get("result", "in progress"), 18 | step=json["currentStep"], 19 | bot=Bot.from_json(json.get("bot", None)), 20 | tiny_id=json.get("tinyID", ""), 21 | date=date) 22 | 23 | def __init__(self, **args): 24 | self.number = args["number"] 25 | self.id = args["id"] 26 | self.tiny_id = args["tiny_id"] 27 | self.result = args["result"] 28 | self.bot = args["bot"] 29 | self.date = args["date"] 30 | self.step = args["step"] 31 | 32 | def __repr__(self): 33 | return self.__dict__.__str__() 34 | 35 | def status(self): 36 | if self.step == "completed": 37 | return self.result 38 | else: 39 | return self.step 40 | 41 | def date_string(self): 42 | temp = self.date.replace(tzinfo=pytz.utc) 43 | local_time = temp.astimezone(get_localzone()) 44 | return local_time.strftime('%d-%b-%Y %H:%M') 45 | 46 | def is_complete(self): 47 | return self.step == "completed" 48 | 49 | def is_pending(self): 50 | return self.step == "pending" 51 | 52 | def succeeded(self): 53 | return self.result == "succeeded" 54 | 55 | def completed_with_warnings(self): 56 | return self.result == "warnings" 57 | -------------------------------------------------------------------------------- /xserverpy/lib/integration_watcher.py: -------------------------------------------------------------------------------- 1 | import time 2 | from xserverpy.utils.porgress import Progress 3 | from xserverpy.utils.config import * 4 | from xserverpy.utils import config 5 | 6 | 7 | class IntegrationWatcher(): 8 | 9 | def __init__(self, integrations_service, integration, interval): 10 | self.integration = integration 11 | self.integrations_service = integrations_service 12 | self.progress = Progress() 13 | self.interval = interval 14 | 15 | def watch(self): 16 | self.handle_pending() 17 | 18 | previous_step = None 19 | while not self.integration.is_complete(): 20 | self.integration = self.integrations_service.get_item(self.integration.id) 21 | 22 | if self.integration.is_complete(): 23 | break 24 | 25 | if previous_step != self.integration.step: 26 | previous_step = self.integration.step 27 | self.progress.increment("- Performing step: %s" % self.integration.step) 28 | 29 | self.progress.step() 30 | time.sleep(self.interval) 31 | 32 | self.progress.done() 33 | print ("") 34 | return self.print_integration_result(self.integration) 35 | 36 | def handle_pending(self): 37 | if self.integration.is_pending(): 38 | self.progress.increment("- Pending for integration") 39 | 40 | self.previos_bot = None 41 | while self.integration.is_pending(): 42 | self.print_running() 43 | 44 | self.integration = self.integrations_service.get_item(self.integration.id) 45 | time.sleep(self.interval * 3) 46 | 47 | def print_running(self): 48 | integrations = self.integrations_service.get_running_integration() 49 | 50 | is_the_only_pending = len(integrations) == 0 or integrations[0].id == self.integration.id 51 | if is_the_only_pending: 52 | self.progress.step() 53 | return 54 | 55 | bot_name = integrations[0].bot.name 56 | if self.previos_bot == bot_name: 57 | pass 58 | else: 59 | self.previos_bot = bot_name 60 | if config.tty: 61 | sys.stdout.write("\r") 62 | sys.stdout.write("\033[K") 63 | sys.stdout.write("Waiting for bot '%s' to finish integrating" % bot_name) 64 | sys.stdout.flush() 65 | print("") 66 | else: 67 | self.progress.increment("Waiting for bot '%s' to finish integrating" % 68 | bot_name, prefix="") 69 | 70 | self.progress.step() 71 | 72 | def print_integration_result(self, integration): 73 | if integration.succeeded(): 74 | success("Integration number '%s' for bot '%s' completed successfully" % 75 | (integration.number, integration.bot.name)) 76 | 77 | result = True 78 | elif integration.completed_with_warnings(): 79 | warn("Integration number '%s' for bot '%s' completed with warnings" % 80 | (integration.number, integration.bot.name)) 81 | result = True 82 | else: 83 | error("Integration number '%s' for bot '%s' failed with result '%s'" % 84 | (integration.number, integration.bot.name, integration.result)) 85 | result = False 86 | 87 | info("Integration ID '%s" % integration.id) 88 | return result 89 | -------------------------------------------------------------------------------- /xserverpy/lib/integrations.py: -------------------------------------------------------------------------------- 1 | from integration import Integration 2 | from base_service import BaseService 3 | from xserverpy.utils.config import * 4 | from xserverpy.utils import config 5 | import json 6 | 7 | 8 | class Integrations(BaseService): 9 | 10 | def __init__(self, settings, bot_id=None): 11 | self.bot_id = bot_id 12 | BaseService.__init__(self, settings) 13 | 14 | def integrate(self): 15 | return self.post() 16 | 17 | def endpoint(self): 18 | if self.bot_id: 19 | return "bots/%s/integrations" % self.bot_id 20 | else: 21 | return "integrations" 22 | 23 | def singe_item_endpoint(self, integration_id): 24 | return "integrations/%s" % integration_id 25 | 26 | def cancel_integration(self, item_id): 27 | url = self.singe_item_endpoint(item_id) + "/cancel" 28 | return self.perform_request(url, "post") 29 | 30 | def item_class(self): 31 | return Integration 32 | 33 | def get_running_integration(self): 34 | content, _ = self.perform_request("integrations/running", "get") 35 | parsed = self.parse_response(content) 36 | parsed = map(lambda x: self.get_item(x.id), parsed) 37 | return parsed 38 | -------------------------------------------------------------------------------- /xserverpy/lib/user.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | class User(): 5 | 6 | def __init__(self, user=None, password=None): 7 | self.user = user 8 | self.password = password 9 | 10 | def basic_auth(self): 11 | if self.user and self.password: 12 | return requests.auth.HTTPBasicAuth(self.user, 13 | self.password) 14 | else: 15 | return None 16 | -------------------------------------------------------------------------------- /xserverpy/lib/xcode_server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | 4 | PORT = 443 5 | 6 | 7 | class XcodeServer(object): 8 | 9 | def __init__(self, host=None, port=PORT): 10 | self.host = host 11 | self.port = port 12 | 13 | self.validate() 14 | 15 | def validate(self): 16 | if not self.host: 17 | return 18 | 19 | if not self.url_validate(): 20 | raise RuntimeError("Host is not a URL, please past a valid host, examples:\n" + 21 | "- http://10.55.55.50\n" + 22 | "- https://10.55.55.50") 23 | 24 | if "/xcode/api" not in self.host: 25 | self.host = self.host + "/xcode/api" 26 | 27 | def url_validate(self): 28 | regex = re.compile( r'^(?:http|ftp)s?://' 29 | r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' 30 | r'localhost|' #localhost... 31 | r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip 32 | r'(?::\d+)?' # optional port 33 | r'(?:/?|[/?]\S+)$', re.IGNORECASE) 34 | return regex.match(self.host) 35 | -------------------------------------------------------------------------------- /xserverpy/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /xserverpy/utils/config.py: -------------------------------------------------------------------------------- 1 | from termcolor import colored 2 | import sys 3 | 4 | verbose = False 5 | tty = True 6 | 7 | 8 | def colored_if_needed(msg, color): 9 | if tty: 10 | return colored(msg, color) 11 | else: 12 | return msg 13 | 14 | 15 | def error(msg): 16 | print(colored_if_needed(msg, "red")) 17 | 18 | 19 | def warn(msg): 20 | print(colored_if_needed(msg, "yellow")) 21 | 22 | 23 | def success(msg): 24 | print(colored_if_needed(msg, "green")) 25 | 26 | 27 | def info(msg): 28 | print(msg) 29 | -------------------------------------------------------------------------------- /xserverpy/utils/porgress.py: -------------------------------------------------------------------------------- 1 | from config import * 2 | import sys 3 | import config 4 | 5 | 6 | class Progress(): 7 | 8 | def __init__(self): 9 | self.steps_done = 0 10 | 11 | def increment(self, title=None, prefix="\n"): 12 | self.print_done_if_needed() 13 | self.step_counter = 0 14 | 15 | if title: 16 | print("%s%s" % (prefix, title)) 17 | 18 | self.steps_done += 1 19 | 20 | def print_done_if_needed(self): 21 | if self.steps_done > 0 and self.step_counter > 0: 22 | if config.tty: 23 | success('\b\bDone ') 24 | else: 25 | success(' Done ') 26 | 27 | def done(self): 28 | if config.tty and self.steps_done > 0: 29 | success('\b\bDone ') 30 | else: 31 | success(' Done ') 32 | 33 | def step(self): 34 | if self.step_counter == 0 and not config.tty: 35 | print 'Loading.', 36 | 37 | if config.tty: 38 | self.progress() 39 | else: 40 | sys.stdout.write('.') 41 | sys.stdout.flush() 42 | 43 | self.step_counter += 1 44 | 45 | def progress(self): 46 | sys.stdout.write("\r") 47 | sys.stdout.write("\033[K") 48 | sys.stdout.write('Loading.... ') 49 | 50 | if (self.step_counter % 4) == 0: 51 | sys.stdout.write('/ ') 52 | elif (self.step_counter % 4) == 1: 53 | sys.stdout.write('- ') 54 | elif (self.step_counter % 4) == 2: 55 | sys.stdout.write('\\ ') 56 | elif (self.step_counter % 4) == 3: 57 | sys.stdout.write('| ') 58 | 59 | sys.stdout.flush() 60 | -------------------------------------------------------------------------------- /xserverpy/utils/settings.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from xserverpy.lib.xcode_server import XcodeServer 4 | from xserverpy.lib.user import User 5 | 6 | 7 | class Settings(): 8 | 9 | def __init__(self, server, user): 10 | self.server = server 11 | self.user = user 12 | 13 | def update(self, server, user): 14 | if server.host: 15 | self.server.host = server.host 16 | if server.port: 17 | self.server.port = server.port 18 | if user.user: 19 | self.user.user = user.user 20 | if user.password: 21 | self.user.password = user.password 22 | 23 | @classmethod 24 | def load(cls, file_name="xserverpy"): 25 | settings = cls.load_file(False, file_name) 26 | if settings: 27 | return settings 28 | 29 | settings = cls.load_file(True, file_name) 30 | if settings: 31 | return settings 32 | 33 | return EmptySettings() 34 | 35 | @classmethod 36 | def load_file(cls, is_global, file_name): 37 | path = cls.storage_path(is_global, file_name) 38 | 39 | if os.path.exists(path): 40 | return cls.parse_file(path) 41 | 42 | return None 43 | 44 | @classmethod 45 | def storage_path(cls, is_global, file_name="xserverpy"): 46 | path = "." + file_name 47 | if is_global: 48 | path = os.path.expanduser("~/" + path) 49 | return path 50 | 51 | @classmethod 52 | def parse_file(cls, file_path): 53 | with open(file_path) as opened: 54 | j = json.loads(opened.read()) 55 | u = User(**j["user"]) 56 | s = XcodeServer(**j["server"]) 57 | return Settings(s, u) 58 | 59 | def store(self, is_global, file_name="xserverpy"): 60 | to_store = {"user": self.user.__dict__, "server": self.server.__dict__} 61 | path = self.storage_path(is_global, file_name) 62 | with open(path, 'w') as outfile: 63 | json.dump(to_store, outfile, indent=4, separators=(',', ': ')) 64 | 65 | def validate(self): 66 | if not self.server.host: 67 | raise RuntimeError("Host is not a URL, please past a valid host, examples:\n" + 68 | "- http://10.55.55.50\n" + 69 | "- https://10.55.55.50") 70 | 71 | if not self.server.port: 72 | raise RuntimeError("Port is missing") 73 | 74 | def is_empty(): 75 | return False 76 | 77 | 78 | class EmptySettings(Settings): 79 | 80 | def __init__(self): 81 | self.user = None 82 | self.server = None 83 | 84 | def is_empty(): 85 | return True 86 | 87 | def update(self, server, user): 88 | self.user = user 89 | self.server = server 90 | -------------------------------------------------------------------------------- /xserverpy/utils/version.py: -------------------------------------------------------------------------------- 1 | VERSION = "0.2.1" 2 | AUTHOR = "Omar Abdelhafith" 3 | VERSION_STRING = "xserverpy: Version %s\nAuthor: %s" % (VERSION, AUTHOR) 4 | -------------------------------------------------------------------------------- /xserverpy/xserverpy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from lib.cli import parse 4 | from display.bots_printer import BotsPrinter 5 | from display.integrations_printer import IntegrationsPrinter 6 | from lib.xcode_server import XcodeServer 7 | from lib.user import User 8 | from lib.bots import Bots 9 | from utils.settings import Settings 10 | from lib.integration_watcher import IntegrationWatcher 11 | from lib.integrations import Integrations 12 | from utils.config import * 13 | from utils import version 14 | import sys 15 | from utils import config 16 | 17 | 18 | def start(): 19 | reload(sys) 20 | sys.setdefaultencoding('utf8') 21 | start_with_args(None) 22 | 23 | 24 | def start_with_args(params): 25 | args = parse(params) 26 | 27 | if args.version: 28 | print_version() 29 | 30 | # try: 31 | if args.bots: 32 | handle_bots(args) 33 | 34 | if args.integrations: 35 | handle_integrations(args) 36 | 37 | if args.cancel: 38 | handle_cancel(args) 39 | 40 | if args.integrate: 41 | handle_integrate(args) 42 | 43 | if args.init: 44 | handle_init(args) 45 | 46 | if args.running: 47 | handle_running(args) 48 | 49 | # except Exception as e: 50 | # error(e) 51 | 52 | 53 | def print_version(): 54 | info(version.VERSION_STRING) 55 | sys.exit(0) 56 | 57 | 58 | def handle_bots(args): 59 | settings = get_updated_settings(args) 60 | b = Bots(settings) 61 | printer = BotsPrinter(b.get_all()) 62 | printer.print_items() 63 | 64 | 65 | def handle_integrations(args): 66 | settings = get_updated_settings(args) 67 | 68 | bot_id = None 69 | if args.bot: 70 | bot_id = get_bot(settings, args).id 71 | 72 | integrations = Integrations(settings, bot_id=bot_id) 73 | IntegrationsPrinter.print_integrations(integrations.get_all()) 74 | 75 | 76 | def handle_integrate(args): 77 | settings = get_updated_settings(args) 78 | bot = get_bot(settings, args) 79 | 80 | config.tty = args.no_tty 81 | 82 | integrations_service = Integrations(settings, bot_id=bot.id) 83 | integration = integrations_service.integrate() 84 | 85 | if args.wait: 86 | add_watcher(integrations_service, integration, bot.name, args.interval) 87 | else: 88 | success("Integration number '%s' for bot '%s' posted successfully" % 89 | (integration.number, bot.name)) 90 | info("Integration ID '%s" % integration.id) 91 | 92 | 93 | def handle_cancel(args): 94 | settings = get_updated_settings(args) 95 | integrations_service = Integrations(settings) 96 | result = integrations_service.cancel_integration(args.id) 97 | 98 | if result: 99 | success("Integration with id '%s' cancelled successfully" % args.id) 100 | else: 101 | error("Failed to cancel integration with id '%s'" % args.id) 102 | 103 | 104 | def handle_init(args): 105 | settings = get_settings(args) 106 | settings.store(not args.local) 107 | path = Settings.storage_path(not args.local) 108 | success("Settings saved successfully to '%s'" % path) 109 | 110 | 111 | def handle_running(args): 112 | settings = get_updated_settings(args) 113 | integrations_service = Integrations(settings) 114 | IntegrationsPrinter.print_running(integrations_service.get_running_integration()) 115 | 116 | 117 | def add_watcher(integrations_service, integration, bot_name, interval): 118 | success("Integration number '%s' for bot '%s' started" % 119 | (integration.number, bot_name)) 120 | info("Integration ID '%s" % integration.id) 121 | 122 | watcher = IntegrationWatcher(integrations_service, integration, interval) 123 | if watcher.watch(): 124 | exit(0) 125 | else: 126 | exit(1) 127 | 128 | 129 | def get_settings(args): 130 | u = User(user=args.user, password=args.password) 131 | s = XcodeServer(host=args.host, port=args.port) 132 | return Settings(s, u) 133 | 134 | 135 | def get_updated_settings(args): 136 | settings = Settings.load() 137 | u = User(user=args.user, password=args.password) 138 | s = XcodeServer(host=args.host, port=args.port) 139 | settings.update(s, u) 140 | settings.validate() 141 | return settings 142 | 143 | 144 | def get_bot(settings, args): 145 | bots_service = Bots(settings) 146 | 147 | method_name = ["get_named", "get_item"][is_id(args.bot)] 148 | method = getattr(bots_service, method_name, None) 149 | bot = method(args.bot) 150 | if not bot: 151 | name_or_id = ["with name", "with id"][is_id(args.bot)] 152 | raise RuntimeError("Bot %s '%s' cannot be found" % (name_or_id, args.bot)) 153 | 154 | return bot 155 | 156 | 157 | def is_id(string): 158 | try: 159 | int(string, 16) 160 | return len(string) > 30 161 | except: 162 | return False 163 | --------------------------------------------------------------------------------