gitsome-0.8.0/000077500000000000000000000000001345243314000131655ustar00rootroot00000000000000gitsome-0.8.0/.gitignore000066400000000000000000000023301345243314000151530ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ #Ipython Notebook .ipynb_checkpoints # temporary files from vim and emacs *~ *# .#* *.swp *.swo # Virtualenv pip-selfcheck.json bin/ include/ # Mac .DS_Store * Misc scratch/ *.egg-info/ *.pyc *.out *.xcf recipe/ .binstar.yml binstar.yml .landscape.yaml .cache/ build/ dist/ gitsome.egg-info/ docs/_build/ .tox/ lexer_table.py parser_table.py parser_test_table.py gitsome/lexer_table.py gitsome/parser_table.py tests/lexer_table.py tests/parser_table.py tests/lexer_test_table.py tests/parser_test_table.py gitsome-0.8.0/.gitsomeconfig000066400000000000000000000006601345243314000160250ustar00rootroot00000000000000[github] user_login = None verify_ssl = True clr_primary = None clr_secondary = green clr_tertiary = cyan clr_quaternary = yellow clr_bold = cyan clr_code = red clr_error = red clr_header = yellow clr_link = green clr_list = cyan clr_message = None clr_num_comments = green clr_num_points = green clr_tag = cyan clr_time = yellow clr_title = None clr_tooltip = None clr_user = cyan clr_view_link = magenta clr_view_index = magenta gitsome-0.8.0/.gitsomeconfigurl000066400000000000000000000000751345243314000165500ustar00rootroot00000000000000[url] url_list = ['https://github.com/octocat/spoon-knife'] gitsome-0.8.0/.travis.yml000066400000000000000000000002611345243314000152750ustar00rootroot00000000000000language: python python: - 3.4 env: - TOXENV=py34 os: - linux install: - travis_retry pip install tox - pip install codecov script: - tox after_success: - codecov gitsome-0.8.0/CHANGELOG.md000066400000000000000000000172201345243314000150000ustar00rootroot00000000000000![](http://i.imgur.com/0SXZ90y.gif) gitsome ======= [![Build Status](https://travis-ci.org/donnemartin/gitsome.svg?branch=master)](https://travis-ci.org/donnemartin/gitsome) [![Codecov](https://img.shields.io/codecov/c/github/donnemartin/gitsome.svg)](https://codecov.io/github/donnemartin/gitsome) [![PyPI version](https://badge.fury.io/py/gitsome.svg)](http://badge.fury.io/py/gitsome) [![PyPI](https://img.shields.io/pypi/pyversions/gitsome.svg)](https://pypi.python.org/pypi/gitsome/) [![License](https://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) To view the latest `README`, `docs`, and `code`, visit the GitHub repo: https://github.com/donnemartin/gitsome To submit bugs or feature requests, visit the issue tracker: https://github.com/donnemartin/gitsome/issues Changelog ========= 0.8.0 (2019-04-07) ------------------ This version adds support for Python 3.7. ### Updates * [#160](https://github.com/donnemartin/gitsome/pull/160) - Add Python 3.7 support. Fixes [#152](https://github.com/donnemartin/gitsome/pull/152), [#144](https://github.com/donnemartin/gitsome/pull/144), [#126](https://github.com/donnemartin/gitsome/pull/126), [#105](https://github.com/donnemartin/gitsome/pull/105) and several other related bugs. * [#147](https://github.com/donnemartin/gitsome/pull/148) - Gracefully ignore missing avatar image, by [kBite](https://github.com/kBite). * [#142](https://github.com/donnemartin/gitsome/pull/142) - Update release checklist. * [#134](https://github.com/donnemartin/gitsome/pull/134) - Update GitHub integrations link. * [#120](https://github.com/donnemartin/gitsome/pull/120) - Add license disclaimer. ### Bug Fixes * [#151](https://github.com/donnemartin/gitsome/pull/151) - Fix gh command typos in docs, by [cyblue9](https://github.com/cyblue9). * [#137](https://github.com/donnemartin/gitsome/pull/137) - Fix Running as a Docker Container anchor in README, by [kamontat](https://github.com/kamontat). * [#129](https://github.com/donnemartin/gitsome/pull/129) - Fix trending command to handle empty summaries, by [emres](https://github.com/emres). * [#123](https://github.com/donnemartin/gitsome/pull/123) - Remove buggy codecov badge. * [#117](https://github.com/donnemartin/gitsome/pull/117) - Fix 0.7.0 CHANGELOG date, by [dbaio](https://github.com/dbaio). 0.7.0 (2017-03-26) ------------------ ### Features * [#99](https://github.com/donnemartin/gitsome/pull/99) - Add Dockerfile to run gitsome in a Docker container, by [l0rd](https://github.com/l0rd) and [larson004](https://github.com/larson004). ### Bug Fixes * [#67](https://github.com/donnemartin/gitsome/pull/67) - Fix `gh_issues` typo in the `README`, by [srisankethu](https://github.com/srisankethu). * [#69](https://github.com/donnemartin/gitsome/pull/69) - Fix `--issue_filter` typo for `gh_issues` command in `COMMANDS.md`. * [#80](https://github.com/donnemartin/gitsome/pull/80) - Fix path for auto completions in `README`. * [#92](https://github.com/donnemartin/gitsome/pull/92) - Fix viewing HTML contents in the terminal for GitHub Enterprise users, by [dongweiming](https://github.com/dongweiming). * [#97](https://github.com/donnemartin/gitsome/pull/97) - Fix error hint from `gh gitignores` to `gh gitignore-templates`, by [zYeoman](https://github.com/zYeoman). * [#116](https://github.com/donnemartin/gitsome/pull/116) - Fix gh trending command resulting in an error. ### Updates * [#58](https://github.com/donnemartin/gitsome/pull/58) - Tweak `README` intro, add logo. * [#74](https://github.com/donnemartin/gitsome/pull/74) - Add link to official GitHub integration page in `README`. * [#79](https://github.com/donnemartin/gitsome/pull/79) - Only store password in config for GitHub Enterprise (due to Enterprise limitations), by [nttibbetts](https://github.com/nttibbetts). * [#86](https://github.com/donnemartin/gitsome/pull/86) - Update dependency info for `uritemplate`. * [#89](https://github.com/donnemartin/gitsome/pull/89) - Fix a bug listing info on repos without a desc field, by [SanketDG](https://github.com/SanketDG). * [#98](https://github.com/donnemartin/gitsome/pull/98) - Prefer GitHub Enterprise token before password. * [#104](https://github.com/donnemartin/gitsome/pull/104) - Update install instructions to use pip3. * [#111](https://github.com/donnemartin/gitsome/pull/111) - Add note about current Python 3.6 incompatibility. * [#115](https://github.com/donnemartin/gitsome/pull/115) - Set current Python support to 3.4 and 3.5. 0.6.0 (2016-05-29) ------------------ ### Features * [#3](https://github.com/donnemartin/gitsome/issues/3) - Add GitHub Enterprise support. * [#33](https://github.com/donnemartin/gitsome/issues/33) - Revamp the info shown with the `gh feed` command. ### Bug Fixes * [#30](https://github.com/donnemartin/gitsome/issues/30) - Fix a typo in the `pip3` install instructions. * [#39](https://github.com/donnemartin/gitsome/issues/39) - Fix `gh feed` `-pr/--private` flag in docs. * [#40](https://github.com/donnemartin/gitsome/issues/40) - Fix `create-issue` `NoneType` error if no `-b/--body` is specified. * [#46](https://github.com/donnemartin/gitsome/issues/46) - Fix `gh view` with the -b/--browser option only working for repos, not for issues or PRs. * [#48](https://github.com/donnemartin/gitsome/issues/48) - Fix `create-repo` `NoneType` error if no `-d/--description` is specified. * [#54](https://github.com/donnemartin/gitsome/pull/54) - Update to `prompt-toolkit` 1.0.0, which includes performance improvements (especially noticeable on Windows) and bug fixes. * Fix `Config` docstrings. ### Updates * [#26](https://github.com/donnemartin/gitsome/issues/26), [#32](https://github.com/donnemartin/gitsome/issues/32) - Add copyright notices for third party libraries. * [#44](https://github.com/donnemartin/gitsome/pull/44), [#53](https://github.com/donnemartin/gitsome/pull/53) - Update packaging dependencies based on semantic versioning. * Tweak `README` intro. 0.5.0 (2016-05-15) ------------------ ### Features * [#12](https://github.com/donnemartin/gitsome/issues/12) - Allow 2FA-enabled users to log in with a password + 2FA code. Previously 2FA-enabled users could only log in with a [personal access token](https://github.com/settings/tokens). Also includes an update of login prompts to improve clarity. ### Bug Fixes * [#16](https://github.com/donnemartin/gitsome/pull/16), [#28](https://github.com/donnemartin/gitsome/pull/28) - Fix typos in README. * [#18](https://github.com/donnemartin/gitsome/pull/18) - Fix dev install instructions in README. * [#24](https://github.com/donnemartin/gitsome/pull/24) - Fix style guide broken link in CONTRIBUTING. ### Updates * [#1](https://github.com/donnemartin/gitsome/issues/1) - Add Codecov coverage testing status to README. * [#2](https://github.com/donnemartin/gitsome/issues/2) - Add note about enabling Zsh completions to README. * [#4](https://github.com/donnemartin/gitsome/issues/4) - Add note about using `pip3` to README. * [#5](https://github.com/donnemartin/gitsome/issues/5) - Decrease speed of README gif. * [#6](https://github.com/donnemartin/gitsome/pull/6) - Update url for `click`. * [#20](https://github.com/donnemartin/gitsome/issues/20) - Add note about enabling more completions to README. * [#21](https://github.com/donnemartin/gitsome/issues/21) - Bump up `prompt-toolkit` version from `0.51` to `0.52`. * [#26](https://github.com/donnemartin/gitsome/issues/26) - Add `xonsh` copyright notice to LICENSE. * [#32](https://github.com/donnemartin/gitsome/pull/32) - Add `github3.py`, `html2text`, and `img2txt` copyright notices to LICENSE. * Update installation instructions in README. * Update color customization discussion in README. 0.4.0 (2016-05-09) ------------------ * Initial release. gitsome-0.8.0/CHANGELOG.rst000066400000000000000000000206251345243314000152130ustar00rootroot00000000000000.. figure:: http://i.imgur.com/0SXZ90y.gif :alt: gitsome ======= |Build Status| |Codecov| |PyPI version| |PyPI| |License| To view the latest ``README``, ``docs``, and ``code``, visit the GitHub repo: https://github.com/donnemartin/gitsome To submit bugs or feature requests, visit the issue tracker: https://github.com/donnemartin/gitsome/issues Changelog ========= 0.8.0 (2019-04-07) ------------------ This version adds support for Python 3.7. Updates ~~~~~~~ - `#160 `__ - Add Python 3.7 support. Fixes `#152 `__, `#144 `__, `#126 `__, `#105 `__ and several other related bugs. - `#147 `__ - Gracefully ignore missing avatar image, by `kBite `__. - `#142 `__ - Update release checklist. - `#134 `__ - Update GitHub integrations link. - `#120 `__ - Add license disclaimer. Bug Fixes ~~~~~~~~~ - `#151 `__ - Fix gh command typos in docs, by `cyblue9 `__. - `#137 `__ - Fix Running as a Docker Container anchor in README, by `kamontat `__. - `#129 `__ - Fix trending command to handle empty summaries, by `emres `__. - `#123 `__ - Remove buggy codecov badge. - `#117 `__ - Fix 0.7.0 CHANGELOG date, by `dbaio `__. 0.7.0 (2017-03-26) ------------------ Features ~~~~~~~~ - `#99 `__ - Add Dockerfile to run gitsome in a Docker container, by `l0rd `__ and `larson004 `__. Bug Fixes ~~~~~~~~~ - `#67 `__ - Fix ``gh_issues`` typo in the ``README``, by `srisankethu `__. - `#69 `__ - Fix ``--issue_filter`` typo for ``gh_issues`` command in ``COMMANDS.md``. - `#80 `__ - Fix path for auto completions in ``README``. - `#92 `__ - Fix viewing HTML contents in the terminal for GitHub Enterprise users, by `dongweiming `__. - `#97 `__ - Fix error hint from ``gh gitignores`` to ``gh gitignore-templates``, by `zYeoman `__. - `#116 `__ - Fix gh trending command resulting in an error. Updates ~~~~~~~ - `#58 `__ - Tweak ``README`` intro, add logo. - `#74 `__ - Add link to official GitHub integration page in ``README``. - `#79 `__ - Only store password in config for GitHub Enterprise (due to Enterprise limitations), by `nttibbetts `__. - `#86 `__ - Update dependency info for ``uritemplate``. - `#89 `__ - Fix a bug listing info on repos without a desc field, by `SanketDG `__. - `#98 `__ - Prefer GitHub Enterprise token before password. - `#104 `__ - Update install instructions to use pip3. - `#111 `__ - Add note about current Python 3.6 incompatibility. - `#115 `__ - Set current Python support to 3.4 and 3.5. 0.6.0 (2016-05-29) ------------------ Features ~~~~~~~~ - `#3 `__ - Add GitHub Enterprise support. - `#33 `__ - Revamp the info shown with the ``gh feed`` command. Bug Fixes ~~~~~~~~~ - `#30 `__ - Fix a typo in the ``pip3`` install instructions. - `#39 `__ - Fix ``gh feed`` ``-pr/--private`` flag in docs. - `#40 `__ - Fix ``create-issue`` ``NoneType`` error if no ``-b/--body`` is specified. - `#46 `__ - Fix ``gh view`` with the -b/--browser option only working for repos, not for issues or PRs. - `#48 `__ - Fix ``create-repo`` ``NoneType`` error if no ``-d/--description`` is specified. - `#54 `__ - Update to ``prompt-toolkit`` 1.0.0, which includes performance improvements (especially noticeable on Windows) and bug fixes. - Fix ``Config`` docstrings. Updates ~~~~~~~ - `#26 `__, `#32 `__ - Add copyright notices for third party libraries. - `#44 `__, `#53 `__ - Update packaging dependencies based on semantic versioning. - Tweak ``README`` intro. 0.5.0 (2016-05-15) ------------------ Features ~~~~~~~~ - `#12 `__ - Allow 2FA-enabled users to log in with a password + 2FA code. Previously 2FA-enabled users could only log in with a `personal access token `__. Also includes an update of login prompts to improve clarity. Bug Fixes ~~~~~~~~~ - `#16 `__, `#28 `__ - Fix typos in README. - `#18 `__ - Fix dev install instructions in README. - `#24 `__ - Fix style guide broken link in CONTRIBUTING. Updates ~~~~~~~ - `#1 `__ - Add Codecov coverage testing status to README. - `#2 `__ - Add note about enabling Zsh completions to README. - `#4 `__ - Add note about using ``pip3`` to README. - `#5 `__ - Decrease speed of README gif. - `#6 `__ - Update url for ``click``. - `#20 `__ - Add note about enabling more completions to README. - `#21 `__ - Bump up ``prompt-toolkit`` version from ``0.51`` to ``0.52``. - `#26 `__ - Add ``xonsh`` copyright notice to LICENSE. - `#32 `__ - Add ``github3.py``, ``html2text``, and ``img2txt`` copyright notices to LICENSE. - Update installation instructions in README. - Update color customization discussion in README. 0.4.0 (2016-05-09) ------------------ - Initial release. .. |Build Status| image:: https://travis-ci.org/donnemartin/gitsome.svg?branch=master :target: https://travis-ci.org/donnemartin/gitsome .. |Codecov| image:: https://img.shields.io/codecov/c/github/donnemartin/gitsome.svg :target: https://codecov.io/github/donnemartin/gitsome .. |PyPI version| image:: https://badge.fury.io/py/gitsome.svg :target: http://badge.fury.io/py/gitsome .. |PyPI| image:: https://img.shields.io/pypi/pyversions/gitsome.svg :target: https://pypi.python.org/pypi/gitsome/ .. |License| image:: https://img.shields.io/:license-apache-blue.svg :target: http://www.apache.org/licenses/LICENSE-2.0.html gitsome-0.8.0/CHECKLIST.md000066400000000000000000000036571345243314000150330ustar00rootroot00000000000000Release Checklist ================= A. Install in a new venv and run unit tests Note, you can't seem to script the virtualenv calls, see: https://bitbucket.org/dhellmann/virtualenvwrapper/issues/219/cant-deactivate-active-virtualenv-from $ deactivate $ rmvirtualenv gitsome $ mkvirtualenv gitsome $ pip install -e . $ pip install -r requirements-dev.txt $ rm -rf .tox && tox B. Run code checks $ scripts/run_code_checks.sh C. Run manual [smoke tests](#smoke-tests) on Mac, Ubuntu, Windows D. Update and review `README.rst` and `Sphinx` docs, then check gitsome/docs/build/html/index.html $ scripts/update_docs.sh E. Push changes F. Review Travis, Codecov, and Gemnasium G. Start a new release branch $ git flow release start x.y.z H. Increment the version number in `gitsome/__init__.py` I. Update and review `CHANGELOG` $ scripts/create_changelog.sh J. Commit the changes K. Finish the release branch $ git flow release finish 'x.y.z' L. Input a tag $ vx.y.z M. Push tagged release to develop and master $ git checkout master $ git push Might need to recreate develop branch. N. Set CHANGELOG.rst as `README.rst` $ scripts/set_changelog_as_readme.sh O. Register package with PyPi $ python setup.py register -r pypi P. Upload to PyPi $ python setup.py sdist upload -r pypi Q. Upload Sphinx docs to PyPi $ python setup.py upload_sphinx R. Review newly released package from PyPi S. Release on GitHub: https://github.com/donnemartin/gitsome/tags 1. Click "Add release notes" for latest release 2. Copy release notes from `CHANGELOG.md` 3. Click "Publish release" T. Install in a new venv and run manual [smoke tests](#smoke-tests) on Mac, Ubuntu, Windows ## Smoke Tests Run the following on Python 3.4: * Craete a new `virtualenv` * Pip install `gitsome` into new `virtualenv` * Run `gitsome` * Run targeted tests based on recent code changes gitsome-0.8.0/COMMANDS.md000066400000000000000000000500511345243314000147110ustar00rootroot00000000000000# Commands ## GitHub Integration Commands Reference Check out the handy [autocompleter with interactive help](https://github.com/donnemartin/gitsome/blob/master/README.md#git-and-github-autocompleter-with-interactive-help) to guide you through each command. ### gh configure Configure `gitsome`. Attempts to authenticate the user and to set up the user's news feed. If `gitsome` has not yet been configured, calling a `gh` command that requires authentication will automatically invoke the `configure` command. Usage/Example(s): $ gh configure For GitHub Enterprise users, run with the `-e/--enterprise` flag: $ gh configure -e #### Authentication To properly integrate with GitHub, you will be asked to enter a user name and either a password or a [personal access token](https://github.com/settings/tokens). If you use two-factor authentication, you will also need to enter your 2FA code, or you can log in with a personal access token. Visit the following page to generate a token: [https://github.com/settings/tokens](https://github.com/settings/tokens) `gitsome` will need the 'repo' and 'user' permissions. ![Imgur](http://i.imgur.com/1C7gBHz.png) #### GitHub Enterprise GitHub Enterprise users will be asked to enter the GitHub Enterprise url and whether they want to verify SSL certificates. #### Authentication Source Code Curious what's going on behind the scenes with authentication? Check out the [authentication source code](https://github.com/donnemartin/gitsome/blob/master/gitsome/config.py#L177-L328). #### User Feed `gitsome` will need your news feed url to run the `gh feed` command with no arguments. ![Imgur](http://i.imgur.com/2LWcyS6.png) To integrate `gitsome` with your news feed, visit the following url while logged into GitHub: [https://github.com](https://github.com) You will be asked to enter the url found when clicking 'Subscribe to your news feed', which will look something like this: https://github.com/donnemartin.private.atom?token=TOKEN ![Imgur](http://i.imgur.com/f2zvdIm.png) ### gh create-comment Create a comment on the given issue. Usage: $ gh create-comment [user_repo_number] [-t/--text] Param(s): ``` :type user_repo_number: str :param user_repo_number: The user/repo/issue_number. ``` Option(s): ``` :type text: str :param text: The comment text. ``` Example(s): $ gh create-comment donnemartin/saws/1 -t "hello world" $ gh create-comment donnemartin/saws/1 --text "hello world" ### gh create-issue Create an issue. Usage: $ gh create-issue [user_repo] [-t/--issue_title] [-d/--issue_desc] Param(s): ``` :type user_repo: str :param user_repo: The user/repo. ``` Option(s): ``` :type issue_title: str :param issue_title: The issue title. :type issue_desc: str :param issue_desc: The issue body (optional). ``` Example(s): $ gh create-issue donnemartin/gitsome -t "title" $ gh create-issue donnemartin/gitsome -t "title" -d "desc" $ gh create-issue donnemartin/gitsome --issue_title "title" --issue_desc "desc" ### gh create-repo Create a repo. Usage: $ gh create-repo [repo_name] [-d/--repo_desc] [-pr/--private] Param(s): ``` :type repo_name: str :param repo_name: The repo name. ``` Option(s): ``` :type repo_desc: str :param repo_desc: The repo description (optional). :type private: bool :param private: Determines whether the repo is private. Default: False. ``` Example(s): $ gh create-repo repo_name $ gh create-repo repo_name -d "desc" $ gh create-repo repo_name --repo_desc "desc" $ gh create-repo repo_name -pr $ gh create-repo repo_name --repo_desc "desc" --private ### gh emails List all the user's registered emails. Usage/Example(s): $ gh emails ### gh emojis List all GitHub supported emojis. Usage: $ gh emojis [-p/--pager] Option(s): ``` :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh emojis $ gh emojis -p $ gh emojis --pager ### gh feed List all activity for the given user or repo. If `user_or_repo` is not provided, uses the logged in user's news feed seen while visiting https://github.com. If `user_or_repo` is provided, shows either the public or `[-pr/--private]` feed activity of the user or repo. Usage: $ gh feed [user_or_repo] [-pr/--private] [-p/--pager] Param(s): ``` :type user_or_repo: str :param user_or_repo: The user or repo to list events for (optional). If no entry, defaults to the logged in user's feed. ``` Option(s): ``` :type private: bool :param private: Determines whether to show the private events (True) or public events (False). :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh feed $ gh feed | grep foo $ gh feed donnemartin $ gh feed donnemartin -pr -p $ gh feed donnemartin --private --pager $ gh feed donnemartin/haxor-news -p #### News Feed ![Imgur](http://i.imgur.com/2LWcyS6.png) #### User Activity Feed ![Imgur](http://i.imgur.com/kryGLXz.png) #### Repo Activity Feed ![Imgur](http://i.imgur.com/d2kxDg9.png) ### gh following List all followed users and the total followed count. Usage: $ gh following [user] [-p/--pager] Param(s): ``` :type user: str :param user: The user login. If None, returns the followed users of the logged in user. ``` Option(s): ``` :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh following $ gh following -p $ gh following octocat --pager ![Imgur](http://i.imgur.com/bjUmbf3.png) Also check out the [`gh user`](#gh-user) command. ### gh followers List all followers and the total follower count. Usage: $ gh followers [user] [-p/--pager] Param(s): ``` :type user: str :param user: The user login (optional). If None, returns the followers of the logged in user. ``` Option(s): ``` :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh followers $ gh followers -p $ gh followers octocat --pager ### gh gitignore-template Output the gitignore template for the given language. Usage: $ gh gitignore-template [language] Param(s): ``` :type language: str :param language: The language. ``` Example(s): $ gh gitignore-template Python $ gh gitignore-template Python > .gitignore ![Imgur](http://i.imgur.com/S5m5ZcO.png) ### gh gitignore-templates Output all supported gitignore templates. Usage: $ gh gitignore-templates [-p/--pager] Option(s): ``` :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh gitignore-templates $ gh gitignore-templates -p $ gh gitignore-templates --pager ![Imgur](http://i.imgur.com/u8qYx1s.png) ### gh issue Output detailed information about the given issue. Usage: $ gh issue [user_repo_number] Param(s): ``` :type user_repo_number: str :param user_repo_number: The user/repo/issue_number. ``` Example(s): $ gh issue donnemartin/saws/1 ![Imgur](http://i.imgur.com/ZFv9MuV.png) ### gh issues List all issues matching the filter. Usage: $ gh issues [-f/--issue_filter] [-s/--issue_state] [-l/--limit] [-p/--pager] Option(s): ``` :type issue_filter: str :param issue_filter: assigned, created, mentioned, subscribed (default). :type issue_state: str :param issue_state: all, open (default), closed. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh issues $ gh issues -f assigned $ gh issues --issue_filter created $ gh issues -s all -l 20 -p $ gh issues --issue_state closed --limit 20 --pager $ gh issues -f created -s all -p ![Imgur](http://i.imgur.com/AB5zxxo.png) ### gh license Output the license template for the given license. Usage: $ gh license [license_name] Param(s): ``` :type license_name: str :param license_name: The license name. ``` Example(s): $ gh license apache-2.0 $ gh license mit > LICENSE ![Imgur](http://i.imgur.com/zJHVxaA.png) ### gh licenses Output all supported license templates. Usage/Licenses: $ gh licenses ![Imgur](http://i.imgur.com/S9SbMLJ.png) ### gh me List information about the logged in user. Displaying the avatar will require [installing the optional `PIL` dependency](#installing-pil). Usage: $ gh me [-b/--browser] [-t/--text_avatar] [-l/--limit] [-p/--pager] Option(s): ``` :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. :type text_avatar: bool :param text_avatar: Determines whether to view the profile avatar in plain text. On Windows this value is always set to True due to lack of support of `img2txt` on Windows. :type limit: int :param limit: The number of user repos to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh me $ gh me -b $ gh me --browser $ gh me -t -l 20 -p $ gh me --text_avatar --limit 20 --pager ![Imgur](http://i.imgur.com/csk5j0S.png) ### gh notifications List all notifications. Usage: $ gh notifications [-l/--limit] [-p/--pager] Option(s): ``` :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh notifications $ gh notifications -l 20 -p $ gh notifications --limit 20 --pager ![Imgur](http://i.imgur.com/uwmwxsW.png) ### gh octo Output an Easter egg or the given message from Octocat. Usage: $ gh octo [say] Param(s): ``` :type say: str :param say: What Octocat should say. If say is None, octocat speaks an Easter egg. ``` Example(s): $ gh octo $ gh octo "foo bar" ![Imgur](http://i.imgur.com/bNzCa5p.png) ### gh pull-request Output detailed information about the given pull request. Usage: $ gh pull-request [user_repo_number] Param(s): ``` :type user_repo_number: str :param user_repo_number: The user/repo/pull_number. ``` Example(s): $ gh pull-request donnemartin/saws/80 ![Imgur](http://i.imgur.com/3MtKjKy.png) ### gh pull-requests List all pull requests. Usage: $ gh pull-requests [-l/--limit] [-p/--pager] Option(s): ``` :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh pull-requests $ gh pull-requests -l 20 -p $ gh pull-requests --limit 20 --pager ![Imgur](http://i.imgur.com/4A2eYM9.png) ### gh rate-limit Output the rate limit. Not available for GitHub Enterprise. Usage/Example(s): $ gh rate-limit ### gh repo Output detailed information about the given repo. Usage: $ gh repo [user_repo] Param(s): ``` :type user_repo: str :param user_repo: The user/repo. ``` Example(s): $ gh repo donnemartin/haxor-news ![Imgur](http://i.imgur.com/XFMpWCI.png) ### gh repos List all repos matching the given filter. Usage: $ gh repos [repo_filter] [-l/--limit] [-p/--pager] Param(s): ``` :type repo_filter: str :param repo_filter: The filter for repo names. Only repos matching the filter will be returned. If None, outputs all the logged in user's repos. ``` Option(s): ``` :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh repos $ gh repos aws $ gh repos aws -l 20 -p $ gh repos aws --limit 20 --pager ![Imgur](http://i.imgur.com/YXWPWma.png) ### gh search-issues Search for all issues matching the given query. For more information about the query qualifiers, visit the [searching issues reference](https://help.github.com/articles/searching-issues/). Usage: $ gh search-issues [query] [-l/--limit] [-p/--pager] Param(s): ``` :type query: str :param query: The search query. The query can contain any combination of the following supported qualifers: - `type` With this qualifier you can restrict the search to issues or pull request only. - `in` Qualifies which fields are searched. With this qualifier you can restrict the search to just the title, body, comments, or any combination of these. - `author` Finds issues created by a certain user. - `assignee` Finds issues that are assigned to a certain user. - `mentions` Finds issues that mention a certain user. - `commenter` Finds issues that a certain user commented on. - `involves` Finds issues that were either created by a certain user, assigned to that user, mention that user, or were commented on by that user. - `state` Filter issues based on whether they’re open or closed. - `labels` Filters issues based on their labels. - `language` Searches for issues within repositories that match a certain language. - `created` or `updated` Filters issues based on times of creation, or when they were last updated. - `comments` Filters issues based on the quantity of comments. - `user` or `repo` Limits searches to a specific user or repository. For more information about these qualifiers, see: http://git.io/d1oELA ``` Option(s): ``` :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh search-issues "foo type:pr author:donnemartin" -l 20 -p $ gh search-issues "foobarbaz in:title created:>=2015-01-01" --limit 20 --pager Additional Example(s): ``` Search issues that have your user name tagged @donnemartin: $ gh search-issues "is:issue donnemartin is:open" -p Search issues that have the most +1s: $ gh search-issues "is:open is:issue sort:reactions-+1-desc" -p Search issues that have the most comments: $ gh search-issues "is:open is:issue sort:comments-desc" -p Search issues with the "help wanted" tag: $ gh search-issues "is:open is:issue label:\"help wanted\"" -p Search all your open private issues: $ gh search-issues "is:open is:issue is:private" -p ``` ![Imgur](http://i.imgur.com/DXXxkBD.png) ### gh search-repos Search for all repos matching the given query. For more information about the query qualifiers, visit the [searching repos reference](https://help.github.com/articles/searching-repositories/). Usage: $ gh search-repos [query] [-s/--sort] [-l/--limit] [-p/--pager] Param(s): ``` :type query: str :param query: The search query. The query can contain any combination of the following supported qualifers: - `in` Qualifies which fields are searched. With this qualifier you can restrict the search to just the repository name, description, readme, or any combination of these. - `size` Finds repositories that match a certain size (in kilobytes). - `forks` Filters repositories based on the number of forks, and/or whether forked repositories should be included in the results at all. - `created` or `pushed` Filters repositories based on times of creation, or when they were last updated. Format: `YYYY-MM-DD`. Examples: `created:<2011`, `pushed:<2013-02`, `pushed:>=2013-03-06` - `user` or `repo` Limits searches to a specific user or repository. - `language` Searches repositories based on the language they're written in. - `stars` Searches repositories based on the number of stars. For more information about these qualifiers, see: http://git.io/4Z8AkA ``` Option(s): ``` :type sort: str :param sort: Optional: 'stars', 'forks', 'updated'. If not specified, sorting is done by query best match. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh search-repos "maps language:python" -s stars -l 20 -p $ gh search-repos "created:>=2015-01-01 stars:>=1000 language:python" --sort stars --limit 20 --pager ![Imgur](http://i.imgur.com/kazXWWY.png) ### gh starred Output starred repos. Usage: $ gh starred [repo_filter] [-l/--limit] [-p/--pager] Param(s): ``` :type repo_filter: str :param repo_filter: The filter for repo names. Only repos matching the filter will be returned. If None, outputs all starred repos. ``` Option(s): ``` :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh starred $ gh starred foo -l 20 -p $ gh starred foo --limit 20 --pager ![Imgur](http://i.imgur.com/JB88Kw8.png) ### gh trending List trending repos for the given language. Usage: $ gh trending [language] [-w/--weekly] [-m/--monthly] [-D/--devs] [-b/--browser] [-p/--pager] Param(s): ``` :type language: str :param language: The language (optional). If blank, shows 'Overall'. ``` Option(s): ``` :type weekly: bool :param weekly: Determines whether to show the weekly rankings. Daily is the default. :type monthly: bool :param monthly: Determines whether to show the monthly rankings. Daily is the default. If both `monthly` and `weekly` are set, `monthly` takes precedence. :type devs: bool :param devs: determines whether to display the trending devs or repos. Only valid with the -b/--browser option. :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh trending $ gh trending Python -w -p $ gh trending Python --weekly --devs --browser $ gh trending --browser ![Imgur](http://i.imgur.com/Dx77HFW.png) ### gh user List information about the given user. Displaying the avatar will require [installing the optional `PIL` dependency](#installing-pil). Usage: $ gh user [user_id] [-b/--browser] [-t/--text_avatar] [-l/--limit] [-p/--pager] Param(s): ``` :type user_id: str :param user_id: The user id/login. If None, returns followers of the logged in user. ``` Option(s): ``` :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. :type text_avatar: bool :param text_avatar: Determines whether to view the profile avatar in plain text instead of ansi (default). On Windows this value is always set to True due to lack of support of `img2txt` on Windows. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. ``` Example(s): $ gh user octocat $ gh user octocat -b $ gh user octocat --browser $ gh user octocat -t -l 10 -p $ gh user octocat --text_avatar --limit 10 --pager ![Imgur](http://i.imgur.com/xVoVPVe.png) ### gh view View the given notification/repo/issue/pull_request/user index in the terminal or a browser. This method is meant to be called after one of the following commands which outputs a table of notifications/repos/issues/pull_requests/users: $ gh repos $ gh search-repos $ gh starred $ gh issues $ gh pull-requests $ gh search-issues $ gh notifications $ gh trending $ gh user $ gh me Usage: $ gh view [index] [-b/--browser] Param(s): ``` :type index: str :param index: Determines the index to view. ``` Option(s): ``` :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. ``` Example(s): $ gh repos $ gh view 1 $ gh starred $ gh view 1 -b $ gh view 1 --browser ![Imgur](http://i.imgur.com/NVEwGbV.png) ### Option: View in a Pager Many `gh` commands support a `-p/--pager` option that displays results in a pager, where available. Usage: $ gh [param] [options] -p $ gh [param] [options] --pager ### Option: View in a Browser Many `gh` commands support a `-b/--browser` option that displays results in your default browser instead of your terminal. Usage: $ gh [param] [options] -b $ gh [param] [options] --browser gitsome-0.8.0/CONTRIBUTING.md000066400000000000000000000042661345243314000154260ustar00rootroot00000000000000Contributing ============ Contributions are welcome! **Please carefully read this page to make the code review process go as smoothly as possible and to maximize the likelihood of your contribution being merged.** ## Bug Reports For bug reports or requests [submit an issue](https://github.com/donnemartin/gitsome/issues). ## Pull Requests The preferred way to contribute is to fork the [main repository](https://github.com/donnemartin/gitsome) on GitHub. 1. Fork the [main repository](https://github.com/donnemartin/gitsome). Click on the 'Fork' button near the top of the page. This creates a copy of the code under your account on the GitHub server. 2. Clone this copy to your local disk: $ git clone git@github.com:YourLogin/gitsome.git $ cd gitsome 3. Create a branch to hold your changes and start making changes. Don't work in the `master` branch! $ git checkout -b my-feature 4. Work on this copy on your computer using Git to do the version control. When you're done editing, run the following to record your changes in Git: $ git add modified_files $ git commit 5. Push your changes to GitHub with: $ git push -u origin my-feature 6. Finally, go to the web page of your fork of the `gitsome` repo and click 'Pull Request' to send your changes for review. ### GitHub Pull Requests Docs If you are not familiar with pull requests, review the [pull request docs](https://help.github.com/articles/using-pull-requests/). ### Code Quality Ensure your pull request satisfies all of the following, where applicable: * Is covered by [unit tests](https://github.com/donnemartin/gitsome#unit-tests-and-code-coverage) * Passes [continuous integration](https://github.com/donnemartin/gitsome#continuous-integration) * Is covered by [documentation](https://github.com/donnemartin/gitsome#documentation) Review the following [style guide](https://google.github.io/styleguide/pyguide.html). Run code checks and fix any issues: $ scripts/run_code_checks.sh ### Installation Refer to the [Installation](https://github.com/donnemartin/gitsome#installation) and [Developer Installation](https://github.com/donnemartin/gitsome#developer-installation) sections. gitsome-0.8.0/Dockerfile000066400000000000000000000016511345243314000151620ustar00rootroot00000000000000########################################################## # # # Build the image: # # docker build -t gitsome . # # # # Run the container: # # docker run -ti --rm -v $(pwd):/src/ \ # # -v ${HOME}/.gitsomeconfig:/root/.gitsomeconfig \ # # -v ${HOME}/.gitconfig:/root/.gitconfig \ # # gitsome # # # ########################################################## FROM python:3.5 RUN pip install Pillow COPY /requirements-dev.txt /gitsome/ WORKDIR /gitsome/ RUN pip install -r requirements-dev.txt COPY / /gitsome/ RUN pip install -e . RUN mkdir /src/ WORKDIR /src/ ENTRYPOINT ["gitsome"] gitsome-0.8.0/LICENSE.txt000066400000000000000000001441661345243314000150240ustar00rootroot00000000000000gitsome is licensed under Apache 2.0: I am providing code and resources in this repository to you under an open source license. Because this is my personal repository, the license you receive to my code and resources is from me and not my employer (Facebook). Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2016 Donne Martin. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. xonsh is licensed under BSD: Copyright 2015, the xonsh developers. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. THIS SOFTWARE IS PROVIDED BY THE XONSH DEVELOPERS ``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 XONSH DEVELOPERS 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. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the stakeholders of the xonsh project or the employers of xonsh developers. github3.py is licensed under BSD: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. img2txt is licensed under BSD: Copyright (c) 2013 - 2015, hit9 All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of img2txt nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 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 OWNER 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. html2text is licensed under GNU: GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . gitsome-0.8.0/PRIVACY.md000066400000000000000000000026601345243314000146300ustar00rootroot00000000000000# Privacy Policy ## What information do we collect? To properly integrate with GitHub, `gitsome` must authenticate with GitHub and/or GitHub Enterprise. When using `gitsome`, you may be asked to enter your: user name, password, two factor authentication token, user access token, news feed url, or GitHub Enterprise url. See the [configure](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-configure) section for more details. You may, however, use features of `gitsome` that do not require GitHub authentication anonymously. ## What do we use your information for? Any of the information we collect from you is used solely to interact with GitHub and/or GitHub Enterprise. Other than to provide integration with GitHub, we do not disclose or transfer your information to outside parties. ## How do we protect your information? All supplied sensitive information is encrypted during transmission to GitHub via Secure Socket Layer (SSL) technology. ## Terms and Conditions Please also visit our Terms and Conditions section establishing the use, disclaimers, and limitations of liability governing the use of `gitsome`. ## Your Consent By using `gitsome`, you consent to our privacy policy. ## Changes to our Privacy Policy If we decide to change our privacy policy, we will post those changes on this page. ## Contacting Us Any questions about this document should be addressed to [Donne Martin](donne.martin@gmail.com). gitsome-0.8.0/README.md000066400000000000000000000701021345243314000144440ustar00rootroot00000000000000

An Official Integration for GitHub and GitHub Enterprise.

gitsome ======= [![Build Status](https://travis-ci.org/donnemartin/gitsome.svg?branch=master)](https://travis-ci.org/donnemartin/gitsome) [![PyPI version](https://badge.fury.io/py/gitsome.svg)](http://badge.fury.io/py/gitsome) [![PyPI](https://img.shields.io/pypi/pyversions/gitsome.svg)](https://pypi.python.org/pypi/gitsome/) [![License](https://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) ## Why `gitsome`? ### The Git Command Line Although the standard Git command line is a great tool to manage your Git-powered repos, it can be **tough to remember the usage** of: * 150+ porcelain and plumbing commands * Countless command-specific options * Resources such as tags and branches The Git command line **does not integrate with GitHub**, forcing you to toggle between command line and browser. ## `gitsome` - A Supercharged Git/GitHub CLI With Autocomplete

`gitsome` aims to supercharge your standard git/shell interface by focusing on: * **Improving ease-of-use** * **Increasing productivity** ### Deep GitHub Integration Not all GitHub workflows work well in a terminal; `gitsome` attempts to target those that do. `gitsome` includes 29 GitHub integrated commands that work with **[ALL](#enabling-gh-tab-completions-outside-of-gitsome)** shells: $ gh [param] [options] * [Quick reference](#github-integration-commands-quick-reference) * [General reference](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md) Run `gh` commands along with [Git-Extras](https://github.com/tj/git-extras/blob/master/Commands.md) and [hub](https://hub.github.com/) commands to unlock even more GitHub integrations! ![Imgur](http://i.imgur.com/sG09AJH.png) ### Git and GitHub Autocompleter With Interactive Help You can run the **optional** shell: $ gitsome to enable **autocompletion** and **interactive help** for the following: * Git commands * Git options * Git branches, tags, etc * [Git-Extras commands](https://github.com/tj/git-extras/blob/master/Commands.md) * [GitHub integration commands](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md) ![Imgur](http://i.imgur.com/08OMNjz.png) ![Imgur](http://i.imgur.com/fHjMwlh.png) ### General Autocompleter `gitsome` autocompletes the following: * Shell commands * Files and directories * Environment variables * Man pages * Python To enable additional autocompletions, check out the [Enabling Bash Completions](#enabling-bash-completions) section. ![Imgur](http://i.imgur.com/hg1dpk6.png) ## Fish-Style Auto-Suggestions `gitsome` supports Fish-style auto-suggestions. Use the `right arrow` key to complete a suggestion. ![Imgur](http://i.imgur.com/ZRaFGpY.png) ## Python REPL `gitsome` is powered by [`xonsh`](https://github.com/scopatz/xonsh), which supports a Python REPL. Run Python commands alongside shell commands: ![Imgur](http://i.imgur.com/NYk7WYO.png) Additional `xonsh` features can be found in the [`xonsh tutorial`](http://xon.sh/tutorial.html). ## Command History `gitsome` keeps track of commands you enter and stores them in `~/.xonsh_history.json`. Use the up and down arrow keys to cycle through the command history. ![Imgur](http://i.imgur.com/wq0caZu.png) ## Customizable Highlighting You can control the ansi colors used for highlighting by updating your `~/.gitsomeconfig` file. Color options include: ``` 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' ``` For no color, set the value(s) to `None`. `white` can appear as light gray on some terminals. ![Imgur](http://i.imgur.com/BN1lfEf.png) ## Available Platforms `gitsome` is available for Mac, Linux, Unix, [Windows](#windows-support), and [Docker](#running-as-docker-container). ## TODO >Not all GitHub workflows work well in a terminal; `gitsome` attempts to target those that do. * Add additional GitHub API integrations `gitsome` is just getting started. Feel free to [contribute!](#contributing) ## Index ### GitHub Integration Commands * [GitHub Integration Commands Syntax](#github-integration-commands-syntax) * [GitHub Integration Commands Listing](#github-integration-commands-listing) * [GitHub Integration Commands Quick Reference](#github-integration-commands-quick-reference) * [GitHub Integration Commands Reference in COMMANDS.md](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md) * [`gh configure`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-configure) * [`gh create-comment`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-create-comment) * [`gh create-issue`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-create-issue) * [`gh create-repo`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-create-repo) * [`gh emails`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-emails) * [`gh emojis`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-emojis) * [`gh feed`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-feed) * [`gh followers`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-followers) * [`gh following`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-following) * [`gh gitignore-template`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-gitignore-template) * [`gh gitignore-templates`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-gitignore-templates) * [`gh issue`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-issue) * [`gh issues`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-issues) * [`gh license`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-license) * [`gh licenses`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-licenses) * [`gh me`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-me) * [`gh notifications`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-notifications) * [`gh octo`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-octo) * [`gh pull-request`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-pull-request) * [`gh pull-requests`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-pull-requests) * [`gh rate-limit`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-rate-limit) * [`gh repo`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-repo) * [`gh repos`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-repos) * [`gh search-issues`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-search-issues) * [`gh search-repos`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-search-repos) * [`gh starred`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-starred) * [`gh trending`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-trending) * [`gh user`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-user) * [`gh view`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-view) * [Option: View in a Pager](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#option-view-in-a-pager) * [Option: View in a Browser](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#option-view-in-a-browser) ### Installation and Tests * [Installation](#installation) * [Pip Installation](#pip-installation) * [Virtual Environment Installation](#virtual-environment-installation) * [Running as a Docker Container](#running-as-a-docker-container) * [Running the `gh configure` Command](#running-the-gh-configure-command) * [For GitHub Enterprise Users](#for-github-enterprise-users) * [Enabling Bash Completions](#enabling-bash-completions) * [Enabling `gh` Tab Completions Outside of `gitsome`](#enabling-gh-tab-completions-outside-of-gitsome) * [For Zsh Users](#for-zsh-users) * [Optional: Installing `PIL` or `Pillow`](#optional-installing-pil-or-pillow) * [Supported Python Versions](#supported-python-versions) * [Supported Platforms](#supported-platforms) * [Windows Support](#windows-support) * [Developer Installation](#developer-installation) * [Continuous Integration](#continuous-integration) * [Unit Tests and Code Coverage](#unit-tests-and-code-coverage) * [Documentation](#documentation) ### Misc * [Contributing](#contributing) * [Credits](#credits) * [Contact Info](#contact-info) * [License](#license) ## GitHub Integration Commands Syntax Usage: $ gh [param] [options] ## GitHub Integration Commands Listing ``` configure Configure gitsome. create-comment Create a comment on the given issue. create-issue Create an issue. create-repo Create a repo. emails List all the user's registered emails. emojis List all GitHub supported emojis. feed List all activity for the given user or repo. followers List all followers and the total follower count. following List all followed users and the total followed count. gitignore-template Output the gitignore template for the given language. gitignore-templates Output all supported gitignore templates. issue Output detailed information about the given issue. issues List all issues matching the filter. license Output the license template for the given license. licenses Output all supported license templates. me List information about the logged in user. notifications List all notifications. octo Output an Easter egg or the given message from Octocat. pull-request Output detailed information about the given pull request. pull-requests List all pull requests. rate-limit Output the rate limit. Not available for Enterprise. repo Output detailed information about the given filter. repos List all repos matching the given filter. search-issues Search for all issues matching the given query. search-repos Search for all repos matching the given query. starred Output starred repos. trending List trending repos for the given language. user List information about the given user. view View the given index in the terminal or a browser. ``` ## GitHub Integration Commands Reference: COMMANDS.md See the [GitHub Integration Commands Reference in COMMANDS.md](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md) for a **detailed discussion** of all GitHub integration commands, parameters, options, and examples. Check out the next section for a **quick reference**. ## GitHub Integration Commands Quick Reference ### Configuring `gitsome` To properly integrate with GitHub, you must first configure `gitsome`: $ gh configure For GitHub Enterprise users, run with the `-e/--enterprise` flag: $ gh configure -e ### Listing Feeds #### Listing Your News Feed $ gh feed ![Imgur](http://i.imgur.com/2LWcyS6.png) #### Listing A User's Activity Feed View your activity feed or another user's activity feed, optionally through a pager with `-p/--pager`. The [pager option](#option-view-in-a-pager) is available for many commands. $ gh feed donnemartin -p ![Imgur](http://i.imgur.com/kryGLXz.png) #### Listing A Repo's Activity Feed $ gh feed donnemartin/gitsome -p ![Imgur](http://i.imgur.com/d2kxDg9.png) ### Listing Notifications $ gh notifications ![Imgur](http://i.imgur.com/uwmwxsW.png) ### Listing Pull Requests View all pull requests for your repos: $ gh pull-requests ![Imgur](http://i.imgur.com/4A2eYM9.png) ### Filtering Issues View all open issues where you have been mentioned: $ gh issues --issue_state open --issue_filter mentioned ![Imgur](http://i.imgur.com/AB5zxxo.png) View all issues, filtering for only those assigned to you, regardless of state (open, closed): $ gh issues --issue_state all --issue_filter assigned For more information about the filter and state qualifiers, visit the [`gh issues`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-issues) reference in [COMMANDS.md](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md). ### Filtering Starred Repos $ gh starred "repo filter" ![Imgur](http://i.imgur.com/JB88Kw8.png) ### Searching Issues and Repos #### Searching Issues Search issues that have the most +1s: $ gh search-issues "is:open is:issue sort:reactions-+1-desc" -p ![Imgur](http://i.imgur.com/DXXxkBD.png) Search issues that have the most comments: $ gh search-issues "is:open is:issue sort:comments-desc" -p Search issues with the "help wanted" tag: $ gh search-issues "is:open is:issue label:\"help wanted\"" -p Search issues that have your user name tagged **@donnemartin**: $ gh search-issues "is:issue donnemartin is:open" -p Search all your open private issues: $ gh search-issues "is:open is:issue is:private" -p For more information about the query qualifiers, visit the [searching issues reference](https://help.github.com/articles/searching-issues/). #### Searching Repos Search all Python repos created on or after 2015, with >= 1000 stars: $ gh search-repos "created:>=2015-01-01 stars:>=1000 language:python" --sort stars -p ![Imgur](http://i.imgur.com/kazXWWY.png) For more information about the query qualifiers, visit the [searching repos reference](https://help.github.com/articles/searching-repositories/). ### Listing Trending Repos and Devs View trending repos: $ gh trending [language] [-w/--weekly] [-m/--monthly] [-d/--devs] [-b/--browser] ![Imgur](http://i.imgur.com/aa1gOg7.png) View trending devs (devs are currently only supported in browser): $ gh trending [language] --devs --browser ### Viewing Content #### The `view` command View the previously listed notifications, pull requests, issues, repos, users etc, with HTML nicely formatted for your terminal, or optionally in your browser: $ gh view [#] [-b/--browser] ![Imgur](http://i.imgur.com/NVEwGbV.png) #### The `issue` command View an issue: $ gh issue donnemartin/saws/1 ![Imgur](http://i.imgur.com/ZFv9MuV.png) #### The `pull-request` command View a pull request: $ gh pull-request donnemartin/awesome-aws/2 ![Imgur](http://i.imgur.com/3MtKjKy.png) ### Setting Up `.gitignore` List all available `.gitignore` templates: $ gh gitignore-templates ![Imgur](http://i.imgur.com/u8qYx1s.png) Set up your `.gitignore`: $ gh gitignore-template Python > .gitignore ![Imgur](http://i.imgur.com/S5m5ZcO.png) ### Setting Up `LICENSE` List all available `LICENSE` templates: $ gh licenses ![Imgur](http://i.imgur.com/S9SbMLJ.png) Set up your or `LICENSE`: $ gh license MIT > LICENSE ![Imgur](http://i.imgur.com/zJHVxaA.png) ### Summoning Octocat Call on Octocat to say the given message or an Easter egg: $ gh octo [say] ![Imgur](http://i.imgur.com/bNzCa5p.png) ### Viewing Profiles #### Viewing A User's Profile $ gh user octocat ![Imgur](http://i.imgur.com/xVoVPVe.png) #### Viewing Your Profile View your profile with the `gh user [YOUR_USER_ID]` command or with the following shortcut: $ gh me ![Imgur](http://i.imgur.com/csk5j0S.png) ### Creating Comments, Issues, and Repos Create a comment: $ gh create-comment donnemartin/gitsome/1 -t "hello world" Create an issue: $ gh create-issue donnemartin/gitsome -t "title" -b "body" Create a repo: $ gh create-repo gitsome ### Option: View in a Pager Many `gh` commands support a `-p/--pager` option that displays results in a pager, where available. Usage: $ gh [param] [options] -p $ gh [param] [options] --pager ### Option: View in a Browser Many `gh` commands support a `-b/--browser` option that displays results in your default browser instead of your terminal. Usage: $ gh [param] [options] -b $ gh [param] [options] --browser See the [COMMANDS.md](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md) for a detailed listing of all GitHub integration commands, parameters, options, and examples. Having trouble remembering these commands? Check out the handy [autocompleter with interactive help](#git-and-github-autocompleter-with-interactive-help) to guide you through each command. *Note, you can combine `gitsome` with other utilities such as [Git-Extras](https://github.com/tj/git-extras/blob/master/Commands.md).* ## Installation ### Pip Installation [![PyPI version](https://badge.fury.io/py/gitsome.svg)](http://badge.fury.io/py/gitsome) [![PyPI](https://img.shields.io/pypi/pyversions/gitsome.svg)](https://pypi.python.org/pypi/gitsome/) `gitsome` is hosted on [PyPI](https://pypi.python.org/pypi/gitsome). The following command will install `gitsome`: $ pip3 install gitsome You can also install the latest `gitsome` from GitHub source which can contain changes not yet pushed to PyPI: $ pip3 install git+https://github.com/donnemartin/gitsome.git If you are not installing in a `virtualenv`, you might need to run with `sudo`: $ sudo pip3 install gitsome #### `pip3` Depending on your setup, you might also want to run `pip3` with the [`-H flag`](http://stackoverflow.com/a/28619739): $ sudo -H pip3 install gitsome For most linux users, `pip3` can be installed on your system using the `python3-pip` package. For example, Ubuntu users can run: $ sudo apt-get install python3-pip See this [ticket](https://github.com/donnemartin/gitsome/issues/4) for more details. ### Virtual Environment Installation You can install Python packages in a [`virtualenv`](http://docs.python-guide.org/en/latest/dev/virtualenvs/) to avoid potential issues with dependencies or permissions. If you are a Windows user or if you would like more details on `virtualenv`, check out this [guide](http://docs.python-guide.org/en/latest/dev/virtualenvs/). Install `virtualenv` and `virtualenvwrapper`: $ pip3 install virtualenv $ pip3 install virtualenvwrapper $ export WORKON_HOME=~/.virtualenvs $ source /usr/local/bin/virtualenvwrapper.sh Create a `gitsome` `virtualenv` and install `gitsome`: $ mkvirtualenv gitsome $ pip3 install gitsome If the `pip` install does not work, you might be running Python 2 by default. Check what version of Python you are running: $ python --version If the call above results in Python 2, find the path for Python 3: $ which python3 # Python 3 path for mkvirtualenv's --python option Install Python 3 if needed. Set the Python version when calling `mkvirtualenv`: $ mkvirtualenv --python [Python 3 path from above] gitsome $ pip3 install gitsome If you want to activate the `gitsome` `virtualenv` again later, run: $ workon gitsome To deactivate the `gitsome` `virtualenv`, run: $ deactivate ### Running as a Docker Container You can run gitsome in a Docker container to avoid installing Python and `pip3` locally. To install Docker check out the [official Docker documentation](https://docs.docker.com/engine/getstarted/step_one/#step-1-get-docker). Once you have docker installed you can run gitsome: $ docker run -ti --rm mariolet/gitsome You can use Docker volumes to let gitsome access your working directory, your local .gitsomeconfig and .gitconfig: $ docker run -ti --rm -v $(pwd):/src/ \ -v ${HOME}/.gitsomeconfig:/root/.gitsomeconfig \ -v ${HOME}/.gitconfig:/root/.gitconfig \ mariolet/gitsome If you are running this command often you will probably want to define an alias: $ alias gitsome="docker run -ti --rm -v $(pwd):/src/ \ -v ${HOME}/.gitsomeconfig:/root/.gitsomeconfig \ -v ${HOME}/.gitconfig:/root/.gitconfig \ mariolet/gitsome" To build the Docker image from sources: $ git clone https://github.com/donnemartin/gitsome.git $ cd gitsome $ docker build -t gitsome . ### Starting the `gitsome` Shell Once installed, run the optional `gitsome` autocompleter with interactive help: $ gitsome Running the optional `gitsome` shell will provide you with autocompletion, interactive help, fish-style suggestions, a Python REPL, etc. ### Running `gh` Commands Run GitHub-integrated commands: $ gh [param] [options] Note: Running the `gitsome` shell is not required to execute `gh` commands. After [installing](#installation) `gitsome` you can run `gh` commands from any shell. ### Running the `gh configure` Command To properly integrate with GitHub, `gitsome` must be properly configured: $ gh configure #### For GitHub Enterprise Users Run with the `-e/--enterprise` flag: $ gh configure -e View more details in the [gh configure](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-configure) section. ### Enabling Bash Completions By default, `gitsome` looks at the following [locations to enable bash completions](https://github.com/donnemartin/gitsome/blob/master/xonsh/environ.py#L123-L131). To add additional bash completions, update the `~/.xonshrc` file with the location of your bash completions. If `~/.xonshrc` does not exist, create it: $ touch ~/.xonshrc For example, if additional completions are found in `/usr/local/etc/my_bash_completion.d/completion.bash`, add the following line in `~/.xonshrc`: ``` $BASH_COMPLETIONS.append('/usr/local/etc/my_bash_completion.d/completion.bash') ``` You will need to restart `gitsome` for the changes to take effect. ### Enabling `gh` Tab Completions Outside of `gitsome` You can run `gh` commands outside of the `gitsome` shell completer. To enable `gh` tab completions for this workflow, copy the [`gh_complete.sh`](https://github.com/donnemartin/gitsome/blob/master/scripts/gh_complete.sh) file locally. Let bash know completion is available for the `gh` command within your current session: $ source /path/to/gh_complete.sh To enable tab completion for all terminal sessions, add the following to your `bashrc` file: source /path/to/gh_complete.sh Reload your `bashrc`: $ source ~/.bashrc Tip: `.` is the short form of `source`, so you can run this instead: $ . ~/.bashrc #### For Zsh Users `zsh` includes a module which is compatible with bash completions. Download the [`gh_complete.sh`](https://github.com/donnemartin/gitsome/blob/master/scripts/gh_complete.sh) file as above and append the following to your `.zshrc`: autoload bashcompinit bashcompinit source /path/to/gh_complete.sh Reload your `zshrc`: $ source ~/.zshrc ### Optional: Installing `PIL` or `Pillow` Displaying the avatar for the `gh me` and `gh user` commands will require installing the optional `PIL` or `Pillow` dependency. Windows* and Mac: $ pip3 install Pillow *See the [Windows Support](#windows-support) section for limitations on the avatar. Ubuntu users, check out these [instructions on askubuntu](http://askubuntu.com/a/272095) ### Supported Python Versions * Python 3.4 * Python 3.5 **Python 3.6 is not currently supported.** See this [ticket](https://github.com/donnemartin/gitsome/issues/105) for more information. `gitsome` is powered by `xonsh` which does not currently support Python 2.x, as discussed in this [ticket](https://github.com/scopatz/xonsh/issues/66). ### Supported Platforms * Mac OS X * Tested on OS X 10.10 * Linux, Unix * Tested on Ubuntu 14.04 LTS * Windows * Tested on Windows 10 ### Windows Support `gitsome` has been tested on Windows 10 with `cmd` and `cmder`. Although you can use the standard Windows command prompt, you'll probably have a better experience with either [cmder](https://github.com/cmderdev/cmder) or [conemu](https://github.com/Maximus5/ConEmu). ![Imgur](http://i.imgur.com/A1VCsjV.png) #### Text Only Avatar The commands [`gh user`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-user) and [`gh me`](https://github.com/donnemartin/gitsome/blob/master/COMMANDS.md#gh-me) will always have the `-t/--text_avatar` flag enabled, since [`img2txt`](#credits) does not support the ansi avatar on Windows. #### Config File On Windows, the `.gitsomeconfig ` file can be found in `%userprofile%`. For example: C:\Users\dmartin\.gitsomeconfig ## Developer Installation If you're interested in contributing to `gitsome`, run the following commands: $ git clone https://github.com/donnemartin/gitsome.git $ cd gitsome $ pip3 install -e . $ pip3 install -r requirements-dev.txt $ gitsome $ gh [param] [options] #### `pip3` If you get an error while installing saying that you need Python 3.4+, it could be because your `pip` command is configured for an older version of Python. To fix this issue, it is recommended to install `pip3`: $ sudo apt-get install python3-pip See this [ticket](https://github.com/donnemartin/gitsome/issues/4) for more details. ### Continuous Integration [![Build Status](https://travis-ci.org/donnemartin/gitsome.svg?branch=master)](https://travis-ci.org/donnemartin/gitsome) Continuous integration details are available on [Travis CI](https://travis-ci.org/donnemartin/gitsome). ### Unit Tests and Code Coverage Run unit tests in your active Python environment: $ python tests/run_tests.py Run unit tests with [tox](https://pypi.python.org/pypi/tox) on multiple Python environments: $ tox ### Documentation Source code documentation will soon be available on [Readthedocs.org](https://readthedocs.org/). Check out the [source docstrings](https://github.com/donnemartin/gitsome/blob/master/gitsome/githubcli.py). Run the following to build the docs: $ scripts/update_docs.sh ## Contributing Contributions are welcome! Review the [Contributing Guidelines](https://github.com/donnemartin/gitsome/blob/master/CONTRIBUTING.md) for details on how to: * Submit issues * Submit pull requests ## Credits * [click](https://github.com/pallets/click) by [mitsuhiko](https://github.com/mitsuhiko) * [github_trends_rss](https://github.com/ryotarai/github_trends_rss) by [ryotarai](https://github.com/ryotarai) * [github3.py](https://github.com/sigmavirus24/github3.py) by [sigmavirus24](https://github.com/sigmavirus24) * [html2text](https://github.com/aaronsw/html2text) by [aaronsw](https://github.com/aaronsw) * [img2txt](https://github.com/hit9/img2txt) by [hit9](https://github.com/hit9) * [python-prompt-toolkit](https://github.com/jonathanslenders/python-prompt-toolkit) by [jonathanslenders](https://github.com/jonathanslenders) * [requests](https://github.com/kennethreitz/requests) by [kennethreitz](https://github.com/kennethreitz) * [xonsh](https://github.com/scopatz/xonsh) by [scopatz](https://github.com/scopatz) ## Contact Info Feel free to contact me to discuss any issues, questions, or comments. My contact info can be found on my [GitHub page](https://github.com/donnemartin). ## License *I am providing code and resources in this repository to you under an open source license. Because this is my personal repository, the license you receive to my code and resources is from me and not my employer (Facebook).* [![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) Copyright 2016 Donne Martin Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gitsome-0.8.0/TOS.md000066400000000000000000000031011345243314000141470ustar00rootroot00000000000000# Terms of Service By using `gitsome`, you are agreeing to be bound by the following terms and conditions ("Terms of Service"). If you are entering into this agreement on behalf of a company or other legal entity, you represent that you have the authority to bind such entity, its affiliates and all users who access `gitsome` through your account to these terms and conditions, in which case the terms "you" or "your" shall refer to such entity, its affiliates and users associated with it. If you do not have such authority, or if you do not agree with these terms and conditions, you must not accept this agreement and may not use `gitsome`. `gitsome` is maintained by [Donne Martin](donne.martin@gmail.com). Donne Martin reserves the right to update and change the Terms of Service from time to time without notice. Any new features that augment or enhance `gitsome`, including the release of new tools and resources, shall be subject to the Terms of Service. Continued use of `gitsome` after any such changes shall constitute your consent to such changes. ## Account Terms * You are responsible for maintaining the security of your GitHub account. * You are responsible for all activity that occurs by using `gitsome`. * You may not use the `gitsome` for any illegal or unauthorized purpose. * You must not, in the use of `gitsome`, violate any laws in your jurisdiction. * Your use of `gitsome` is at your sole risk. ## Copyright `gitsome` is copyright ©2016 Donne Martin. ## Questions Any questions about this document should be addressed to [Donne Martin](donne.martin@gmail.com). gitsome-0.8.0/appveyor.yml000066400000000000000000000015641345243314000155630ustar00rootroot00000000000000# What Python version is installed where: # http://www.appveyor.com/docs/installed-software#python environment: matrix: - PYTHON: "C:\\Python27" TOX_ENV: "py27" - PYTHON: "C:\\Python33" TOX_ENV: "py33" - PYTHON: "C:\\Python34" TOX_ENV: "py34" - PYTHON: "C:\\Python35" TOX_ENV: "py35" init: - "%PYTHON%/python -V" - "%PYTHON%/python -c \"import struct;print( 8 * struct.calcsize(\'P\'))\"" install: - "%PYTHON%/Scripts/easy_install -U pip" - "%PYTHON%/Scripts/pip install tox" - "%PYTHON%/Scripts/pip install wheel" build: false # Not a C# project, build stuff at the test step instead. test_script: - "%PYTHON%/Scripts/tox -e %TOX_ENV%" after_test: - "%PYTHON%/python setup.py bdist_wheel" - ps: "ls dist" artifacts: - path: dist\* #on_success: # - TODO: upload the content of dist/*.whl to a public wheelhousegitsome-0.8.0/codecov.yml000066400000000000000000000001661345243314000153350ustar00rootroot00000000000000comment: layout: header, changes, diff coverage: ignore: - gitsome/lib/* - gitsome/compat.py gitsome-0.8.0/gitsome/000077500000000000000000000000001345243314000146345ustar00rootroot00000000000000gitsome-0.8.0/gitsome/__init__.py000066400000000000000000000011311345243314000167410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2015 Donne Martin. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. __version__ = '0.8.0' gitsome-0.8.0/gitsome/compat.py000066400000000000000000000020101345243314000164620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2015 Donne Martin. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import sys import urllib try: # Python 3 import configparser from urllib.parse import urlparse from urllib.request import urlretrieve from urllib.error import URLError except ImportError: # Python 2 import ConfigParser as configparser from urlparse import urlparse from urllib import urlretrieve from urllib2 import URLError if sys.version_info < (3, 3): import HTMLParser else: import html as HTMLParser gitsome-0.8.0/gitsome/completer.py000066400000000000000000000202141345243314000171770ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2015 Donne Martin. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from __future__ import unicode_literals from __future__ import print_function from prompt_toolkit.completion import Completer, Completion from .completions import COMPLETIONS_GH, SUBCOMMANDS from .completions_git import META_LOOKUP_GIT, META_LOOKUP_GIT_EXTRAS from .utils import TextUtils class CompleterGitsome(Completer): """Gitsome prompt toolkit completer. :type text_utils: :class:`utils.TextUtils` :param text_utils: An instance of `utils.TextUtils`. :type fuzzy_match: bool :param fuzzy_match: Determines whether to use fuzzy matching. Currently, this is always set to False but can be enabled. """ def __init__(self): self.fuzzy_match = False self.text_utils = TextUtils() def build_completions_with_meta(self, line, prefix, completions): """Build prompt_toolkit Completions with meta info. :type line: list :param line: The input text as a list of words. :type prefix: string :param prefix: The current word. :type completions: list :param completions: Completions to build meta info for. :rtype: list :return: Completions with meta info. """ completions_with_meta = [] tokens = line.split(' ') if tokens[0] != 'gh': for comp in completions: display = None display_meta = None if 'git' in line and comp.strip() in META_LOOKUP_GIT: display_meta = META_LOOKUP_GIT[comp.strip()] elif 'git' in line and comp.strip() in META_LOOKUP_GIT_EXTRAS: display_meta = META_LOOKUP_GIT_EXTRAS[comp.strip()] completions_with_meta.append( Completion(comp, -len(prefix), display=display, display_meta=display_meta)) return completions_with_meta def completing_command(self, words, word_before_cursor): """Determine if we are currently completing the gh command. :type words: list :param words: The input text broken into word tokens. :type word_before_cursor: str :param word_before_cursor: The current word before the cursor, which might be one or more blank spaces. :rtype: bool :return: Specifies whether we are currently completing the gh command. """ if len(words) == 1 and word_before_cursor != '': return True else: return False def completing_subcommand(self, words, word_before_cursor): """Determine if we are currently completing a subcommand. :type words: list :param words: The input text broken into word tokens. :type word_before_cursor: str :param word_before_cursor: The current word before the cursor, which might be one or more blank spaces. :rtype: bool :return: Specifies whether we are currently completing a subcommand. """ if (len(words) == 1 and word_before_cursor == '') \ or (len(words) == 2 and word_before_cursor != ''): return True else: return False def completing_arg(self, words, word_before_cursor): """Determine if we are currently completing an arg. :type words: list :param words: The input text broken into word tokens. :type word_before_cursor: str :param word_before_cursor: The current word before the cursor, which might be one or more blank spaces. :rtype: bool :return: Specifies whether we are currently completing an arg. """ if (len(words) == 2 and word_before_cursor == '') \ or (len(words) == 3 and word_before_cursor != ''): return True else: return False def completing_subcommand_option(self, words, word_before_cursor): """Determine if we are currently completing an option. :type words: list :param words: The input text broken into word tokens. :type word_before_cursor: str :param word_before_cursor: The current word before the cursor, which might be one or more blank spaces. :rtype: list :return: A list of options. """ options = [] for subcommand, args_opts in COMPLETIONS_GH.items(): if subcommand in words and \ (words[-2] == subcommand or self.completing_subcommand_option_util(subcommand, words)): options.extend(COMPLETIONS_GH[subcommand]['opts']) return options def completing_subcommand_option_util(self, option, words): """Determine if we are currently completing an option. Called by completing_subcommand_option as a utility method. :type words: list :param words: The input text broken into word tokens. :type word_before_cursor: str :param word_before_cursor: The current word before the cursor, which might be one or more blank spaces. :rtype: bool :return: Specifies whether we are currently completing an option. """ # Example: Return True for: gh view 1 --pag if len(words) > 3: if option in words: return True return False def arg_completions(self, words, word_before_cursor): """Generates arguments completions based on the input. :type words: list :param words: The input text broken into word tokens. :type word_before_cursor: str :param word_before_cursor: The current word before the cursor, which might be one or more blank spaces. :rtype: list :return: A list of completions. """ if 'gh' not in words: return [] for subcommand, args_opts in COMPLETIONS_GH.items(): if subcommand in words: args = list(COMPLETIONS_GH[subcommand]['args'].keys()) if not args: # Some commands don't have args, complete options instead. args = list(COMPLETIONS_GH[subcommand]['opts'].keys()) return args if args else [] return [] def get_completions(self, document, _): """Get completions for the current scope. :type document: :class:`prompt_toolkit.Document` :param document: An instance of `prompt_toolkit.Document`. :type _: :class:`prompt_toolkit.completion.Completion` :param _: (Unused). :rtype: generator :return: Yields an instance of `prompt_toolkit.completion.Completion`. """ word_before_cursor = document.get_word_before_cursor(WORD=True) words = self.text_utils.get_tokens(document.text) commands = [] if len(words) == 0: return commands if self.completing_command(words, word_before_cursor): commands = ['gh'] else: if 'gh' not in words: return commands if self.completing_subcommand(words, word_before_cursor): commands = list(SUBCOMMANDS.keys()) else: if self.completing_arg(words, word_before_cursor): commands = self.arg_completions(words, word_before_cursor) else: commands = self.completing_subcommand_option( words, word_before_cursor) completions = self.text_utils.find_matches( word_before_cursor, commands, fuzzy=self.fuzzy_match) return completions gitsome-0.8.0/gitsome/completions.py000066400000000000000000000315271345243314000175520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2015 Donne Martin. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. COMPLETIONS_GH = { 'configure': { 'desc': "Configures gitsome.", 'args': {}, 'opts': { '-e': 'flag (opt) configure GitHub Enterprise.', '--enterprise': 'flag (opt) configure GitHub Enterprise.', }, }, 'create-comment': { 'desc': 'Creates a comment on the given issue.', 'args': { 'octocat/Spoon-Knife/1': 'str (req) user/repo/issue_number combo.', }, 'opts': { '-t': 'see associated -- option for details.', '--text': 'str (req) comment text.', }, }, 'create-issue': { 'desc': 'Creates an issue.', 'args': { 'octocat/Spoon-Knife': 'str (req) user/repository combo.', }, 'opts': { '-t': 'see associated -- option for details.', '--issue_title': 'str (req) issue title.', '-d': 'str (opt) issue description.', '--issue_desc': 'str (opt) issue description.', }, }, 'create-repo': { 'desc': 'Creates a repository.', 'args': { 'Spoon-Knife': 'str (req) repository name.', }, 'opts': { '-d': 'str (opt) repo description', '--repo_desc': 'str (opt) repo description.', '-pr': 'flag (opt) create a private repo', '--private': 'flag (opt) create a private repo', }, }, 'emails': { 'desc': "Lists all the user's registered emails.", 'args': {}, 'opts': {}, }, 'emojis': { 'desc': 'Lists all GitHub supported emojis.', 'args': {}, 'opts': { '-p': 'flag (req) show results in a pager.', '--pager': 'flag (req) show results in a pager.', }, }, 'feed': { 'desc': "Lists all activity for the given user or repo, if called with no arg, shows the logged in user's feed.", 'args': { 'octocat/Hello-World --pager': "str (opt) user or user/repository combo, if blank, shows the logged in user's feed.", }, 'opts': { '-pr': 'flag (req) also show private events.', '--private': 'flag (req) also show private events.', '-p': 'flag (req) show results in a pager.', '--pager': 'flag (req) show results in a pager.', }, }, 'followers': { 'desc': 'Lists all followers and the total follower count.', 'args': { 'octocat': "str (req) the user's login id, if blank, shows logged in user's info.", }, 'opts': { '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'following': { 'desc': 'Lists all followed users and the total followed count.', 'args': { 'octocat': "str (req) the user's login id, if blank, shows logged in user's info.", }, 'opts': { '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'gitignore-template': { 'desc': 'Outputs the gitignore template for the given language.', 'args': { 'Python': 'str (req) the language-specific .gitignore.', }, 'opts': {}, }, 'gitignore-templates': { 'desc': 'Outputs all supported gitignore templates.', 'args': {}, 'opts': { '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'issue': { 'desc': 'Outputs detailed information about the given issue.', 'args': { 'octocat/Spoon-Knife/1': 'str (req) user/repo/issue_number combo.', }, 'opts': {}, }, 'issues': { 'desc': 'Lists all issues matching the filter.', 'args': {}, 'opts': { '-f': 'str (opt) "assigned", "created", "mentioned", "subscribed" (default).', '--issue_filter': 'str (opt) "assigned", "created", "mentioned", "subscribed" (default).', '-s': 'str (opt) "all", "open" (default), "closed".', '--issue_state': 'str (opt) "all", "open" (default), "closed".', '-l': 'flag (opt) num items to show, defaults to 1000.', '--limit': 'flag (opt) num items to show, defaults to 1000.', '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'license': { 'desc': 'Outputs the license template for the given license.', 'args': { 'apache-2.0': 'str (req) the license name.', }, 'opts': {}, }, 'licenses': { 'desc': 'Outputs all supported license templates.', 'args': {}, 'opts': {}, }, 'me': { 'desc': 'Lists information about the logged in user.', 'args': {}, 'opts': { '-b': 'flag (opt) view profile in a browser instead of the terminal.', '--browser': 'flag (opt) view profile in a browser instead of the terminal.', '-t': 'see associated -- option for details.', '--text_avatar': 'flag (opt) view profile pic in plain text.', '-l': 'flag (opt) num items to show, defaults to 1000.', '--limit': 'flag (opt) num items to show, defaults to 1000.', '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'notifications': { 'desc': 'Lists all notifications.', 'args': {}, 'opts': { '-l': 'flag (opt) num items to show, defaults to 1000.', '--limit': 'flag (opt) num items to show, defaults to 1000.', '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'octo': { 'desc': 'Outputs an Easter egg or the given message from Octocat.', 'args': { '"Keep it logically awesome"': 'str (req) a message from Octocat, if empty, Octocat speaks an Easter egg.', }, 'opts': {}, }, 'pull-request': { 'desc': 'Outputs detailed information about the given pull request.', 'args': { 'octocat/Spoon-Knife/3': 'str (req) user/repo/pull_number combo.', }, 'opts': {}, }, 'pull-requests': { 'desc': 'Lists all pull requests.', 'args': {}, 'opts': { '-l': 'flag (opt) num items to show, defaults to 1000.', '--limit': 'flag (opt) num items to show, defaults to 1000.', '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'rate-limit': { 'desc': 'Outputs the rate limit.', 'args': {}, 'opts': {}, }, 'repo': { 'desc': 'Outputs detailed information about the given repo.', 'args': { 'octocat/Spoon-Knife': 'str (req) user/repository combo.', }, 'opts': {}, }, 'repos': { 'desc': 'Lists all repos matching the given filter.', 'args': { '"foo bar optional filter"': 'str (opt) filters repos by name.', }, 'opts': { '-l': 'flag (opt) num items to show, defaults to 1000.', '--limit': 'flag (opt) num items to show, defaults to 1000.', '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'search-issues': { 'desc': 'Searches for all issues matching the given query.', 'args': { '"foobarbaz in:title created:>=2015-01-01"': 'str (req) the search query.', }, 'opts': { '-s': 'str (opt) "stars", "forks", "updated", if blank, sorts by query best match.', '--sort': 'str (opt) "stars", "forks", "updated", if blank, sorts by query best match.', '-l': 'flag (opt) num items to show, defaults to 1000.', '--limit': 'flag (opt) num items to show, defaults to 1000.', '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'search-repos': { 'desc': 'Searches for all repos matching the given query.', 'args': { '"created:>=2015-01-01 stars:>=1500 language:python"': 'str (req) the search query.', }, 'opts': { '-s': 'str (opt) "stars", "forks", "updated", if blank, sorts by query best match.', '--sort': 'str (opt) "stars", "forks", "updated", if blank, sorts by query best match.', '-l': 'flag (opt) num items to show, defaults to 1000.', '--limit': 'flag (opt) num items to show, defaults to 1000.', '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'starred': { 'desc': 'Outputs starred repos.', 'args': { '"foo bar optional filter"': 'str (opt) filters repos by name.', }, 'opts': { '-l': 'flag (opt) num items to show, defaults to 1000.', '--limit': 'flag (opt) num items to show, defaults to 1000.', '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'trending': { 'desc': 'Lists trending repos for the given language.', 'args': { 'Overall': 'str (opt) the language filter for trending repos, if blank, the overall rankings are shown.', }, 'opts': { '-w': 'flag (opt) show the weekly trending repos.', '--weekly': 'flag (opt) show the weekly trending repos.', '-m': 'flag (opt) show the monthly trending repos.', '--monthly': 'flag (opt) show the monthly trending repos.', '-D': 'flag (opt) view trending devs. Only valid with -b/--browser.', '--devs': 'flag (opt) view trending devs. Only valid with -b/--browser.', '-b': 'flag (opt) view profile in a browser instead of the terminal.', '--browser': 'flag (opt) view profile in a browser instead of the terminal.', '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'user': { 'desc': 'Lists information about the given user.', 'args': { 'github --pager': "str (req) the user's login id.", }, 'opts': { '-b': 'flag (opt) view profile in a browser instead of the terminal.', '--browser': 'flag (opt) view profile in a browser instead of the terminal.', '-t': 'see associated -- option for details.', '--text_avatar': 'flag (opt) view profile pic in plain text.', '-l': 'flag (opt) num items to show, defaults to 1000.', '--limit': 'flag (opt) num items to show, defaults to 1000.', '-p': 'flag (opt) show results in a pager.', '--pager': 'flag (opt) show results in a pager.', }, }, 'view': { 'desc': 'Views the given repo or issue index in the terminal or a browser.', 'args': { '1': 'int (req) the 1-based index to view.', }, 'opts': { '-b': 'flag (opt) view profile in a browser instead of the terminal.', '--browser': 'flag (opt) view profile in a browser instead of the terminal.', }, }, } META_LOOKUP_GH = { '10': 'limit: int (opt) limits the posts displayed', '"(?i)(Python|Django)"': ('regex_query: string (opt) applies a regular ' 'expression comment filter'), '1': 'index: int (req) views the post index', '"user"': 'user:string (req) shows info on the specified user', 'gh': 'Git auto-completer with GitHub integration.', } SUBCOMMANDS = {} def build_meta_lookups(): for subcommand, args_opts in COMPLETIONS_GH.items(): META_LOOKUP_GH.update({subcommand: COMPLETIONS_GH[subcommand]['desc']}) SUBCOMMANDS.update({subcommand: COMPLETIONS_GH[subcommand]['desc']}) for opt, meta in args_opts['opts'].items(): META_LOOKUP_GH.update({opt: meta}) for arg, meta in args_opts['args'].items(): META_LOOKUP_GH.update({arg: meta}) build_meta_lookups() gitsome-0.8.0/gitsome/completions_git.py000066400000000000000000000444501345243314000204140ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2015 Donne Martin. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. META_LOOKUP_GIT = { 'git': 'The stupid content tracker.', '--version': 'Prints the Git suite version that the git program came from.', '--help': 'Prints the synopsis and a list of the most commonly used commands.', '-C': 'Run as if git was started in instead of the current working directory.', '-c': 'Pass a configuration parameter to the command.', '--exec-path': 'Path to wherever your core Git programs are installed.', '--html-path': "Print the path, without trailing slash, where Git's HTML documentation is installed and exit.", '--man-path': 'Print the manpath (see man(1)) for the man pages for this version of Git and exit.', '--info-path': 'Print the path where the Info files documenting this version of Git are installed and exit.', '-p': 'Pipe all output into less (or if set, $PAGER) if standard output is a terminal.', '--paginate': 'Pipe all output into less (or if set, $PAGER) if standard output is a terminal.', '--no-pager': 'Do not pipe Git output into a pager.', '--git-dir=': 'Set the path to the repository. This can also be controlled by setting the GIT_DIR environment variable.', '--work-tree=': 'Set the path to the working tree. It can be an absolute path or a path relative to the current working directory.', '--namespace=': 'Set the Git namespace. See gitnamespaces(7) for more details.', '--bare': 'Treat the repository as a bare repository. If GIT_DIR environment is not set, it is set to the current working directory.', '--no-replace-objects': 'Do not use replacement refs to replace Git objects.', '--literal-pathspecs': 'Treat pathspecs literally (i.e. no globbing, no pathspec magic).', '--glob-pathspecs': 'Add "glob" magic to all pathspec.', '--noglob-pathspecs': 'Add "literal" magic to all pathspec.', '--icase-pathspecs': 'Add "icase" magic to all pathspec.', 'add': 'Add file contents to the index.', 'am': 'Apply a series of patches from a mailbox.', 'amend': 'Amend the previous commit.', 'archive': 'Create an archive of files from a named tree.', 'bisect': 'Use binary search to find the commit that introduced a bug.', 'branch': 'List, create, or delete branches.', 'branches': 'List all branches, including remotes.', 'bundle': 'Move objects and refs by archive.', 'c': 'Alias for git clone.', 'ca': 'Alias for git add -A && git commit -av.', 'checkout': 'Switch branches or restore working tree files.', 'cherry-pick': 'Apply the changes introduced by some existing commits.', 'citool': 'Graphical alternative to git-commit.', 'clean': 'Remove untracked files from the working tree.', 'clone': 'Clone a repository into a new directory.', 'commit': 'Record changes to the repository.', 'contributors': 'List repo contributors.', 'describe': 'Describe a commit using the most recent tag reachable from it.', 'diff': 'Show changes between commits, commit and working tree, etc.', 'fetch': 'Download objects and refs from another repository.', 'flow': 'Git branching model extensions.', 'format-patch': 'Prepare patches for e-mail submission.', 'gc': 'Cleanup unnecessary files and optimize the local repository.', 'grep': 'Print lines matching a pattern.', 'gui': 'A portable graphical interface to Git.', 'init': 'Create an empty Git repository or reinitialize an existing one.', 'log': 'Show commit logs.', 'merge': 'Join two or more development histories together.', 'mv': 'Move or rename a file, a directory, or a symlink.', 'notes': 'Add or inspect object notes.', 'pull': 'Fetch from and integrate with another repository or a local branch.', 'push': 'Update remote refs along with associated objects.', 'rebase': 'Forward-port local commits to the updated upstream head.', 'reset': 'Reset current HEAD to the specified state.', 'revert': 'Revert some existing commits.', 'rm': 'Remove files from the working tree and from the index.', 'shortlog': 'Summarize git log output.', 'show': 'Show various types of objects.', 'stage': 'Add file contents to the staging area.', 'stash': 'Stash the changes in a dirty working directory away.', 'status': 'Show the working tree status.', 'submodule': 'Initialize, update or inspect submodules.', 'tag': 'Create, list, delete or verify a tag object signed with GPG.', 'tags': 'List all tags.', 'worktree': 'Manage multiple working trees.', 'gitk': 'The Git repository browser.', 'config': 'Get and set repository or global options.', 'fast-export': 'Git data exporter.', 'fast-import': 'Backend for fast Git data importers.', 'filter-branch': 'Rewrite branches.', 'mergetool': 'Run merge conflict resolution tools to resolve merge conflicts.', 'pack-refs': 'Pack heads and tags for efficient repository access.', 'prune': 'Prune all unreachable objects from the object database.', 'reflog': 'Manage reflog information.', 'relink': 'Hardlink common objects in local repositories.', 'remote': 'Manage set of tracked repositories.', 'remotes': 'List set of tracked repositories.', 'repack': 'Pack unpacked objects in a repository.', 'replace': 'Create, list, delete refs to replace objects.', 'annotate': 'Annotate file lines with commit information.', 'blame': 'Show what revision and author last modified each line of a file.', 'cherry': 'Find commits yet to be applied to upstream.', 'count-objects': 'Count unpacked number of objects and their disk consumption.', 'difftool': 'Show changes using common diff tools.', 'fsck': 'Verifies the connectivity and validity of the objects in the database.', 'get-tar-commit-id': 'Extract commit ID from an archive created using git-archive.', 'help': 'Display help information about Git.', 'instaweb': 'Instantly browse your working repository in gitweb.', 'merge-tree': 'Show three-way merge without touching index.', 'rerere': 'Reuse recorded resolution of conflicted merges.', 'rev-parse': 'Pick out and massage parameters.', 'show-branch': 'Show branches and their commits.', 'verify-commit': 'Check the GPG signature of commits.', 'verify-tag': 'Check the GPG signature of tags.', 'whatchanged': 'Show logs with difference each commit introduces.', 'gitweb': 'Git web interface (web frontend to Git repositories).', 'archimport': 'Import an Arch repository into Git.', 'cvsexportcommit': 'Export a single commit to a CVS checkout.', 'cvsimport': 'Salvage your data out of another SCM people love to hate.', 'cvsserver': 'A CVS server emulator for Git.', 'imap-send': 'Send a collection of patches from stdin to an IMAP folder.', 'p4': 'Import from and submit to Perforce repositories.', 'quiltimport': 'Applies a quilt patchset onto the current branch.', 'request-pull': 'Generates a summary of pending changes.', 'send-email': 'Send a collection of patches as emails.', 'svn': 'Bidirectional operation between a Subversion repository and Git.', 'apply': 'Apply a patch to files and/or to the index.', 'checkout-index': 'Copy files from the index to the working tree.', 'commit-tree': 'Create a new commit object.', 'hash-object': 'Compute object ID and optionally creates a blob from a file.', 'index-pack': 'Build pack index file for an existing packed archive.', 'lfs': 'Git extension for versioning large files.', 'merge-file': 'Run a three-way file merge.', 'merge-index': 'Run a merge for files needing merging.', 'mktag': 'Creates a tag object.', 'mktree': 'Build a tree-object from ls-tree formatted text.', 'pack-objects': 'Create a packed archive of objects.', 'prune-packed': 'Remove extra objects that are already in pack files.', 'read-tree': 'Reads tree information into the index.', 'symbolic-ref': 'Read, modify and delete symbolic refs.', 'unpack-objects': 'Unpack objects from a packed archive.', 'update-index': 'Register file contents in the working tree to the index.', 'update-ref': 'Update the object name stored in a ref safely.', 'write-tree': 'Create a tree object from the current index.', 'cat-file': 'Provide content or type and size information for repository objects.', 'diff-files': 'Compares files in the working tree and the index.', 'diff-index': 'Compare a tree to the working tree or index.', 'diff-tree': 'Compares the content and mode of blobs found via two tree objects.', 'for-each-ref': 'Output information on each ref.', 'ls-files': 'Show information about files in the index and the working tree.', 'ls-remote': 'List references in a remote repository.', 'ls-tree': 'List the contents of a tree object.', 'merge-base': 'Find as good common ancestors as possible for a merge.', 'name-rev': 'Find symbolic names for given revs.', 'pack-redundant': 'Find redundant pack files.', 'rev-list': 'Lists commit objects in reverse chronological order.', 'show-index': 'Show packed archive index.', 'show-ref': 'List references in a local repository.', 'unpack-file': "Creates a temporary file with a blob's contents.", 'var': 'Show a Git logical variable.', 'verify-pack': 'Validate packed Git archive files.', 'daemon': 'A really simple server for Git repositories.', 'fetch-pack': 'Receive missing objects from another repository.', 'http-backend': 'Server side implementation of Git over HTTP.', 'send-pack': 'Push objects over Git protocol to another repository.', 'update-server-info': 'Update auxiliary info file to help dumb servers.', 'http-fetch': 'Download from a remote Git repository via HTTP.', 'http-push': 'Push objects over HTTP/DAV to another repository.', 'parse-remote': 'Routines to help parsing remote repository access parameters.', 'receive-pack': 'Receive what is pushed into the repository.', 'shell': 'Restricted login shell for Git-only SSH access.', 'upload-archive': 'Send archive back to git-archive.', 'upload-pack': 'Send objects packed back to git-fetch-pack.', 'check-attr': 'Display gitattributes information.', 'check-ignore': 'Debug gitignore / exclude files.', 'check-mailmap': 'Show canonical names and email addresses of contacts.', 'check-ref-format': 'Ensures that a reference name is well formed.', 'column': 'Display data in columns.', 'credential': 'Retrieve and store user credentials.', 'credential-cache': 'Helper to temporarily store passwords in memory.', 'credential-store': 'Helper to store credentials on disk.', 'fmt-merge-msg': 'Produce a merge commit message.', 'interpret-trailers': 'help add structured information into commit messages.', 'mailinfo': 'Extracts patch and authorship from a single e-mail message.', 'mailsplit': 'Simple UNIX mbox splitter program.', 'merge-one-file': 'The standard helper program to use with git-merge-index.', 'patch-id': 'Compute unique ID for a patch.', 'sh-i18n': "Git's i18n setup code for shell scripts.", 'sh-setup': 'Common Git shell script setup code.', 'stripspace': 'Remove unnecessary whitespace.', 'subtree': 'Merge subtrees together and split repository into subtrees.', 'git-cvsserver': 'A CVS server emulator for Git.', 'git-credential-osxkeychain': 'Cache GitHub credentials in the OS X Keychain.', 'git-lfs': 'Git extension for versioning large files.', 'git-receive-pack': 'Receive what is pushed into the repository', 'git-shell': 'Restricted login shell for Git-only SSH access.', 'git-subtree': 'Merge subtrees together and split repository into subtrees.', 'git-upload-archive': 'Send archive back to git-archive.', 'git-upload-pack': 'Send objects packed back to git-fetch-pack.', 'git-flow': 'Git branching model extensions.', 'gitchangelog': 'Generates a changelog from git tags and commit messages.', } META_LOOKUP_GIT_EXTRAS = { 'git-extras': 'Various Git utilities.', 'git-alias': 'Define, search and show aliases.', 'git-archive-file': 'Export the current HEAD of the git repository to a archive.', 'git-authors': 'Generate authors report.', 'git-back': 'Undo and Stage latest commits.', 'git-bug': 'Create bug branch.', 'git-changelog': 'Generate a changelog report.', 'git-chore': 'Create chore branch.', 'git-clear': 'Rigorously clean up a repository.', 'git-commits-since': 'Show commit logs since some date.', 'git-contrib': "Show user's contributions", 'git-count': 'Show commit count.', 'git-create-branch': 'Create branches.', 'git-delete-branch': 'Delete branches.', 'git-delete-merged-branches': 'Delete merged branches.', 'git-delete-submodule': 'Delete submodules.', 'git-delete-tag': 'Delete tags.', 'git-delta': 'Lists changed files.', 'git-effort': 'Show effort statistics on file(s).', 'git-feature': 'Create/Merge feature branch.', 'git-fork': 'Fork a repo on github.', 'git-fresh-branch': 'Create fresh branches.', 'git-gh-pages': 'Create the GitHub Pages branch.', 'git-graft': 'Merge and destroy a given branch.', 'git-guilt': 'calculate change between two revisions.', 'git-ignore-io': 'Get sample gitignore file.', 'git-ignore': 'Add .gitignore patterns.', 'git-info': 'Returns information on current repository.', 'git-line-summary': 'Show repository summary by line.', 'git-local-commits': 'List local commits.', 'git-lock': 'Lock a file excluded from version control.', 'git-locked': 'ls files that have been locked.', 'git-merge-into': 'Merge one branch into another.', 'git-merge-repo': 'Merge two repo histories.', 'git-missing': 'Show commits missing from another branch.', 'git-obliterate': 'Completely remove a file from the repository, including past commits and tags.', 'git-pr': 'Checks out a pull request locally.', 'git-psykorebase': 'Rebase a branch with a merge commit.', 'git-pull-request': 'Checks out a pull request from GitHub.', 'git-rebase-patch': 'Rebases a patch.', 'git-refactor': 'Create refactor branch.', 'git-release': 'Commit, tag and push changes to the repository.', 'git-rename-tag': 'Rename a tag.', 'git-repl': 'git read-eval-print-loop.', 'git-reset-file': 'Reset one file.', 'git-root': 'show path of root.', 'git-rscp': 'Copies specific files from the working directory of a remote repository to the current working directory.', 'git-scp': 'Copy files to SSH compatible git-remote.', 'git-sed': 'replace patterns in git-controlled files.', 'git-setup': 'Set up a git repository.', 'git-show-merged-branches': 'Show merged branches.', 'git-show-tree': 'show branch tree of commit history.', 'git-show-unmerged-branches': 'Show unmerged branches.', 'git-squash': 'Import changes from a branch.', 'git-summary': 'Show repository summary.', 'git-touch': 'Touch and add file to the index.', 'git-undo': 'Remove latest commits.', 'git-unlock': 'Unlock a file excluded from version control.', 'extras': 'Various Git utilities.', 'alias': 'Define, search and show aliases.', 'archive-file': 'Export the current HEAD of the git repository to a archive.', 'authors': 'Generate authors report.', 'back': 'Undo and Stage latest commits.', 'bug': 'Create bug branch.', 'changelog': 'Generate a changelog report.', 'chore': 'Create chore branch.', 'clear': 'Rigorously clean up a repository.', 'commits-since': 'Show commit logs since some date.', 'contrib': "Show user's contributions", 'count': 'Show commit count.', 'create-branch': 'Create branches.', 'delete-branch': 'Delete branches.', 'delete-merged-branches': 'Delete merged branches.', 'delete-submodule': 'Delete submodules.', 'delete-tag': 'Delete tags.', 'delta': 'Lists changed files.', 'effort': 'Show effort statistics on file(s).', 'feature': 'Create/Merge feature branch.', 'fork': 'Fork a repo on github.', 'fresh-branch': 'Create fresh branches.', 'gh-pages': 'Create the GitHub Pages branch.', 'graft': 'Merge and destroy a given branch.', 'guilt': 'calculate change between two revisions.', 'ignore-io': 'Get sample gitignore file.', 'ignore': 'Add .gitignore patterns.', 'info': 'Returns information on current repository.', 'line-summary': 'Show repository summary by line.', 'local-commits': 'List local commits.', 'lock': 'Lock a file excluded from version control.', 'locked': 'ls files that have been locked.', 'merge-into': 'Merge one branch into another.', 'merge-repo': 'Merge two repo histories.', 'missing': 'Show commits missing from another branch.', 'obliterate': 'Completely remove a file from the repository, including past commits and tags.', 'pr': 'Checks out a pull request locally.', 'psykorebase': 'Rebase a branch with a merge commit.', 'pull-request': 'Checks out a pull request from GitHub.', 'rebase-patch': 'Rebases a patch.', 'refactor': 'Create refactor branch.', 'release': 'Commit, tag and push changes to the repository.', 'rename-tag': 'Rename a tag.', 'repl': 'git read-eval-print-loop.', 'reset-file': 'Reset one file.', 'root': 'show path of root.', 'rscp': 'Copies specific files from the working directory of a remote repository to the current working directory.', 'scp': 'Copy files to SSH compatible git-remote.', 'sed': 'replace patterns in git-controlled files.', 'setup': 'Set up a git repository.', 'show-merged-branches': 'Show merged branches.', 'show-tree': 'show branch tree of commit history.', 'show-unmerged-branches': 'Show unmerged branches.', 'squash': 'Import changes from a branch.', 'summary': 'Show repository summary.', 'touch': 'Touch and add file to the index.', 'undo': 'Remove latest commits.', 'unlock': 'Unlock a file excluded from version control.', } gitsome-0.8.0/gitsome/config.py000066400000000000000000000704011345243314000164550ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2015 Donne Martin. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from __future__ import unicode_literals from __future__ import print_function import click from getpass import getpass import os import requests from requests.packages.urllib3.exceptions import InsecureRequestWarning from .compat import configparser from .lib.github3 import authorize, enterprise_login, login from .lib.github3.exceptions import AuthenticationFailed, UnprocessableEntity class Config(object): """Gitsome config. :type api: :class:`github3.github.Github` :param api: An instance of github3.github.Github. :type clr_x: str :param clr_x: Various ansi color config colors to use for highlights. :type CONFIG: str :param CONFIG: The config file name. :type CONFIG_SECTION: str :param CONFIG_SECTION: The main config file section label. :type CONFIG_CLR_X: str :param CONFIG_CLR_X: Various ansi color config labels to use for highlights. :type CONFIG_ENTERPRISE_URL: str :param CONFIG_ENTERPRISE_URL: The GitHub Enterprise url. :type CONFIG_USER_LOGIN: str :param CONFIG_USER_LOGIN: The user login. :type CONFIG_USER_PASS: str :param CONFIG_USER_PASS: The user password. :type CONFIG_USER_TOKEN: str :param CONFIG_USER_TOKEN: The user token. :type CONFIG_USER_FEED: str :param CONFIG_USER_FEED: The user feed config. This is the feed on https://github.com/ when logged in and requires the basic auth model, which doesn't work when logging in with tokens or 2FA. This config listed the pre-signed url to access the feed. :type CONFIG_URL: str :param CONFIG_URL: The config file name that contains urls used in the `gh view` command. :type CONFIG_URL_SECTION: str :param CONFIG_URL_SECTION: The config file section that contains urls used in the `gh view [url_index]` command. :type CONFIG_URL_LIST: str :param CONFIG_URL_LIST: The config containing a list of the last set of urls the user has seen, which allows the user to quickly access a repo url with the `gh view [url_index]` command. :type CONFIG_VERIFY_SSL: str :param CONFIG_VERIFY_SSL: Determines whether to verify SSL certs. :type enterprise_url: str :param enterprise_url: The GitHub Enterprise url. :type urls: list :param urls: The last set of urls the user has seen, which allows the user to quickly access a repo url with the gh view [url_index] command. :type user_login: str :param user_login: The user's login in ~/.gitsomeconfig. :type user_pass: str :param user_pass: The user's pass in ~/.gitsomeconfig. This is only stored for GitHub Enterprise users since using only a personal access token does not seem to be supported. :type user_token: str :param user_token: The user's token in ~/.gitsomeconfig. :type verify_ssl: bool :param verify_ssl: Determines whether to verify SSL certs. """ CONFIG = '.gitsomeconfig' CONFIG_CLR_PRIMARY = 'clr_primary' CONFIG_CLR_SECONDARY = 'clr_secondary' CONFIG_CLR_TERTIARY = 'clr_tertiary' CONFIG_CLR_QUATERNARY = 'clr_quaternary' CONFIG_CLR_BOLD = 'clr_bold' CONFIG_CLR_CODE = 'clr_code' CONFIG_CLR_ERROR = 'clr_error' CONFIG_CLR_HEADER = 'clr_header' CONFIG_CLR_LINK = 'clr_link' CONFIG_CLR_LIST = 'clr_list' CONFIG_CLR_MESSAGE = 'clr_message' CONFIG_CLR_NUM_COMMENTS = 'clr_num_comments' CONFIG_CLR_NUM_POINTS = 'clr_num_points' CONFIG_CLR_TAG = 'clr_tag' CONFIG_CLR_TIME = 'clr_time' CONFIG_CLR_TITLE = 'clr_title' CONFIG_CLR_TOOLTIP = 'clr_tooltip' CONFIG_CLR_USER = 'clr_user' CONFIG_CLR_VIEW_LINK = 'clr_view_link' CONFIG_CLR_VIEW_INDEX = 'clr_view_index' CONFIG_SECTION = 'github' CONFIG_USER_LOGIN = 'user_login' CONFIG_USER_PASS = 'user_pass' CONFIG_USER_TOKEN = 'user_token' CONFIG_USER_FEED = 'user_feed' CONFIG_ENTERPRISE_URL = 'enterprise_url' CONFIG_VERIFY_SSL = 'verify_ssl' CONFIG_URL = '.gitsomeconfigurl' CONFIG_URL_SECTION = 'url' CONFIG_URL_LIST = 'url_list' CONFIG_AVATAR = '.gitsomeconfigavatar.png' def __init__(self): self.api = None self.user_login = None self.user_pass = None self.user_token = None self.user_feed = None self.enterprise_url = None self.verify_ssl = True self.urls = [] self._init_colors() self.load_configs([ self.load_config_colors, ]) self.login = login self.authorize = authorize self.getpass = getpass def _init_colors(self): """Initialize colors to their defaults.""" self.clr_primary = None self.clr_secondary = 'green' self.clr_tertiary = 'cyan' self.clr_quaternary = 'yellow' self.clr_bold = 'cyan' self.clr_code = 'cyan' self.clr_error = 'red' self.clr_header = 'yellow' self.clr_link = 'green' self.clr_list = 'cyan' self.clr_message = None self.clr_num_comments = 'green' self.clr_num_points = 'green' self.clr_tag = 'cyan' self.clr_time = 'yellow' self.clr_title = None self.clr_tooltip = None self.clr_user = 'cyan' self.clr_view_link = 'magenta' self.clr_view_index = 'magenta' def authenticate_cached_credentials(self, config, parser, enterprise_auth=enterprise_login): """Authenticate with the user's credentials in ~/.gitsomeconfig. :type config: str :param config: The config path. :type parser: :class:`ConfigParser.RawConfigParser` :param parser: An instance of `ConfigParser.RawConfigParser. """ with open(config) as config_file: try: parser.read_file(config_file) except AttributeError: parser.readfp(config_file) self.user_login = self.load_config( parser=parser, cfg_label=self.CONFIG_USER_LOGIN) self.user_pass = self.load_config( parser=parser, cfg_label=self.CONFIG_USER_PASS) self.user_token = self.load_config( parser=parser, cfg_label=self.CONFIG_USER_TOKEN) self.enterprise_url = self.load_config( parser=parser, cfg_label=self.CONFIG_ENTERPRISE_URL) self.verify_ssl = self.load_config( parser=parser, cfg_label=self.CONFIG_VERIFY_SSL, boolean_config=True) self.user_feed = self.load_config( parser=parser, cfg_label=self.CONFIG_USER_FEED) if not self.verify_ssl: # The user has chosen not to verify SSL certs. # Disable warnings related to this option. requests.packages.urllib3.disable_warnings( InsecureRequestWarning) login_kwargs = { 'username': self.user_login, 'two_factor_callback': self.request_two_factor_code, } if self.enterprise_url is not None: self.login = enterprise_auth login_kwargs.update({ 'url': self.enterprise_url, 'verify': self.verify_ssl, }) if self.user_token is not None: login_kwargs.update({'token': self.user_token}) elif self.user_pass is not None: login_kwargs.update({'password': self.user_pass}) else: self.print_auth_error() return else: login_kwargs.update({'token': self.user_token}) self.api = self.login(**login_kwargs) def authenticate(self, enterprise=False, enterprise_auth=enterprise_login, overwrite=False): """Log into GitHub. Adapted from https://github.com/sigmavirus24/github-cli. :type enterprise: bool :param enterprise: Determines whether to configure GitHub Enterprise. Default: False. :type overwrite: bool :param overwrite: indicates whether we cant to overwrite the current set of credentials. Default: False. """ if self.api is not None and not overwrite: return # Get the full path to the configuration file. config = self.get_github_config_path(self.CONFIG) parser = configparser.RawConfigParser() # Check to make sure the file exists and we are allowed to read it. # Skip if we want to overwrite the auth settings. if os.path.isfile(config) and os.access(config, os.R_OK | os.W_OK) and \ not overwrite: with open(config) as config_file: try: parser.read_file(config_file) except AttributeError: parser.readfp(config_file) self.authenticate_cached_credentials(config, parser) else: # The file didn't exist or we don't have the correct permissions. login_kwargs = { 'two_factor_callback': self.request_two_factor_code, } if enterprise: self.login = enterprise_auth while not self.enterprise_url: self.enterprise_url = input('Enterprise URL: ') if click.confirm('Do you want to verify SSL certs?', default=True): self.verify_ssl = True else: self.verify_ssl = False login_kwargs.update({ 'url': self.enterprise_url, 'verify': self.verify_ssl, }) while not self.user_login: self.user_login = input('User Login: ') login_kwargs.update({'username': self.user_login}) if click.confirm(('Do you want to log in with a password [Y] or ' 'a personal access token [n]?'), default=True): user_pass = None while not user_pass: user_pass = self.getpass('Password: ') login_kwargs.update({'password': user_pass}) try: if not enterprise: # Trade the user password for a personal access token. # This does not seem to be available for Enterprise. auth = self.authorize( self.user_login, user_pass, scopes=['user', 'repo'], note='gitsome', note_url='https://github.com/donnemartin/gitsome', two_factor_callback=self.request_two_factor_code ) self.user_token = auth.token else: self.user_pass = user_pass except (UnprocessableEntity, AuthenticationFailed): click.secho('Error creating token.', fg=self.clr_error) click.secho(('Visit the following page and verify you do ' 'not have an existing token named "gitsome":\n' ' https://github.com/settings/tokens\n' 'If a token already exists, update your ' '~/.gitsomeconfig file with your token:\n' ' user_token = TOKEN\n' 'You can also generate a new token.'), fg=self.clr_message) self.print_auth_error() return else: # The user has chosen to authenticate with a token. while not self.user_token: self.user_token = input('Token: ') login_kwargs.update({'token': self.user_token}) self.api = self.login(**login_kwargs) if self.user_feed: parser.set(self.CONFIG_SECTION, self.CONFIG_USER_FEED, self.user_feed) def check_auth(self): """Check if the current authorization is valid. This method uses the ratelimit_remaining api to check whether the currently authenticated user's credentials are valid without deducting from the rate limit. The ratelimit_remaining api does not seem to be available for GitHub Enterprise. github3.py's method check_authorization seems to only work given an authorization created by a registered application. TODO: Determine a better way to check the authorization for GitHub Enterprise. :type enterprise: bool :param enterprise: Determines whether we are authenticating with GitHub Enterprise. """ if self.enterprise_url is not None: return True try: if self.api is not None: # Throws AuthenticationFailed if invalid credentials but # does not deduct from the rate limit. self.api.ratelimit_remaining return True else: self.print_auth_error() except AuthenticationFailed: self.print_auth_error() return False def get_github_config_path(self, config_file_name): """Attempt to find the github config file. Adapted from https://github.com/sigmavirus24/github-cli. :type config_file_name: str :param config_file_name: The config file name. :rtype: str :return: The github config file path. """ home = os.path.abspath(os.environ.get('HOME', '')) config_file_path = os.path.join(home, config_file_name) return config_file_path def load_config(self, parser, cfg_label, default=None, color_config=False, boolean_config=False): """Load the specified config from ~/.gitsomeconfig. :type parser: :class:`ConfigParser.RawConfigParser` :param parser: An instance of `ConfigParser.RawConfigParser`. :type cfg_label: str :param cfg_label: The config label to load. :type default: str :param default: The default color if no color config exists. Default: None. :type color_config: bool :param color_config: Determines whether this is a color config. Default: False. :type boolean_config: bool :param boolean_config: Determines whether to load a boolean config. Default: False. """ try: if boolean_config: cfg = parser.getboolean(self.CONFIG_SECTION, cfg_label) else: cfg = parser.get(self.CONFIG_SECTION, cfg_label) if color_config: if cfg == 'none': cfg = None # Check if the user input a valid color. # If invalid, this will throw a TypeError click.style('', fg=cfg) except (TypeError, configparser.NoOptionError): return default return cfg def load_configs(self, config_funcs): """Load the specified config from ~/.gitsomeconfig. :type foo: list :param foo: The config methods to run. """ config_file_path = self.get_github_config_path(self.CONFIG) parser = configparser.RawConfigParser() try: with open(config_file_path) as config_file: try: parser.read_file(config_file) except AttributeError: parser.readfp(config_file) for config_func in config_funcs: config_func(parser) except IOError: # There might not be a cache yet, just silently return. return None def load_config_colors(self, parser): """Load the color config from ~/.gitsomeconfig. :type parser: :class:`ConfigParser.RawConfigParser` :param parser: An instance of `ConfigParser.RawConfigParser`. """ self.load_colors(parser) def load_colors(self, parser): """Load all colors from ~/.gitsomeconfig. :type parser: :class:`ConfigParser.RawConfigParser` :param parser: An instance of `ConfigParser.RawConfigParser`. """ self.clr_primary = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_PRIMARY, default=self.clr_primary, color_config=True) self.clr_secondary = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_SECONDARY, default=self.clr_secondary, color_config=True) self.clr_tertiary = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_TERTIARY, default=self.clr_tertiary, color_config=True) self.clr_quaternary = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_QUATERNARY, default=self.clr_quaternary, color_config=True) self.clr_bold = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_BOLD, default=self.clr_bold, color_config=True) self.clr_code = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_CODE, default=self.clr_code, color_config=True) self.clr_code = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_ERROR, default=self.clr_code, color_config=True) self.clr_header = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_HEADER, default=self.clr_header, color_config=True) self.clr_link = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_LINK, default=self.clr_link, color_config=True) self.clr_list = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_LIST, default=self.clr_list, color_config=True) self.clr_message = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_MESSAGE, default=self.clr_message, color_config=True) self.clr_num_comments = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_NUM_COMMENTS, default=self.clr_num_comments, color_config=True) self.clr_num_points = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_NUM_POINTS, default=self.clr_num_points, color_config=True) self.clr_tag = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_TAG, default=self.clr_tag, color_config=True) self.clr_time = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_TIME, default=self.clr_time, color_config=True) self.clr_title = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_TITLE, default=self.clr_title, color_config=True) self.clr_tooltip = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_TOOLTIP, default=self.clr_tooltip, color_config=True) self.clr_user = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_USER, default=self.clr_user, color_config=True) self.clr_view_link = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_VIEW_LINK, default=self.clr_view_link, color_config=True) self.clr_view_index = self.load_config( parser=parser, cfg_label=self.CONFIG_CLR_VIEW_INDEX, default=self.clr_view_index, color_config=True) def load_urls(self, view_in_browser): """Load the current set of urls from ~/.gitsomeconfigurl. :type view_in_browser: bool :param view_in_browser: Determines whether to view the urls in a browser. :rtype: list :return: Collection of urls. """ config = self.get_github_config_path(self.CONFIG_URL) parser = configparser.RawConfigParser() with open(config) as config_file: try: parser.read_file(config_file) except AttributeError: parser.readfp(config_file) urls = parser.get(self.CONFIG_URL_SECTION, self.CONFIG_URL_LIST) urls = urls.strip() excludes = ['[', ']', "'"] for exclude in excludes: urls = urls.replace(exclude, '') if not view_in_browser: urls = urls.replace('https://github.com/', '') return urls.split(', ') def print_auth_error(self): """Print a message the authorization has failed.""" click.secho('Authentication error.', fg=self.clr_error) click.secho(('Update your credentials in ~/.gitsomeconfig ' 'or run:\n gh configure'), fg=self.clr_message) def prompt_news_feed(self): """Prompt the user to enter a news feed url.""" if click.confirm(('No feed url detected.\n Calling gh events without ' "an argument\n displays the logged in user's " 'news feed.\nDo you want gitsome to track your ' 'news feed?'), default=True): click.secho(('Visit the following url while logged into GitHub:\n' ' https://github.com\n' 'Enter the url found under "Subscribe to your ' 'news feed".'), fg=self.clr_message) self.user_feed = '' while not self.user_feed: self.user_feed = input('URL: ') def request_two_factor_code(self): """Request two factor authentication code. Callback if two factor authentication is requested. :rtype: str :return: The user input two factor authentication code. """ code = '' while not code: code = input('Enter 2FA code: ') return code def save_config(self): """Saves the config to ~/.gitsomeconfig.""" if self.check_auth(): config = self.get_github_config_path(self.CONFIG) parser = configparser.RawConfigParser() parser.add_section(self.CONFIG_SECTION) parser.set(self.CONFIG_SECTION, self.CONFIG_USER_LOGIN, self.user_login) if self.user_token is not None: parser.set(self.CONFIG_SECTION, self.CONFIG_USER_TOKEN, self.user_token) if self.user_feed is not None: parser.set(self.CONFIG_SECTION, self.CONFIG_USER_FEED, self.user_feed) if self.enterprise_url is not None: parser.set(self.CONFIG_SECTION, self.CONFIG_ENTERPRISE_URL, self.enterprise_url) if self.user_pass is not None: parser.set(self.CONFIG_SECTION, self.CONFIG_USER_PASS, self.user_pass) else: parser.remove_option(self.CONFIG_SECTION, self.CONFIG_USER_PASS) parser.set(self.CONFIG_SECTION, self.CONFIG_VERIFY_SSL, self.verify_ssl) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_PRIMARY, self.clr_primary) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_SECONDARY, self.clr_secondary) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_TERTIARY, self.clr_tertiary) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_QUATERNARY, self.clr_quaternary) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_BOLD, self.clr_bold) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_CODE, self.clr_code) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_ERROR, self.clr_error) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_HEADER, self.clr_header) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_LINK, self.clr_link) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_LIST, self.clr_list) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_MESSAGE, self.clr_message) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_NUM_COMMENTS, self.clr_num_comments) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_NUM_POINTS, self.clr_num_points) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_TAG, self.clr_tag) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_TIME, self.clr_time) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_TITLE, self.clr_title) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_TOOLTIP, self.clr_tooltip) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_USER, self.clr_user) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_VIEW_LINK, self.clr_view_link) parser.set(self.CONFIG_SECTION, self.CONFIG_CLR_VIEW_INDEX, self.clr_view_index) with open(config, 'w+') as config_file: parser.write(config_file) def save_urls(self): """Save the current set of urls to ~/.gitsomeconfigurl.""" config = self.get_github_config_path(self.CONFIG_URL) parser = configparser.RawConfigParser() try: parser.add_section(self.CONFIG_URL_SECTION) except configparser.DuplicateSectionError: pass parser.set(self.CONFIG_URL_SECTION, self.CONFIG_URL_LIST, self.urls) with open(config, 'w+') as config_file: parser.write(config_file) def show_bash_completions_info(self): """Show info on how to enable bash completions""" click.secho(('By default, gitsome looks at the following locations ' 'to enable bash completions:\n' ' https://github.com/donnemartin/gitsome/blob/master/xonsh/environ.py#L123-L130\n' # NOQA 'If bash completions are not working for you, check out ' 'the following link:\n' ' https://github.com/donnemartin/gitsome#enabling-bash-completions'), # NOQA fg=self.clr_message) gitsome-0.8.0/gitsome/formatter.py000066400000000000000000000600701345243314000172140ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2015 Donne Martin. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from __future__ import unicode_literals from __future__ import print_function import re from .lib.pretty_date_time import pretty_date_time import click class Formatter(object): """Handle formatting of isssues, repos, threads, etc. :type config: :class:`config.Config` :param config: An instance of config.Config. :type event_handlers: dict :param event_handlers: A mapping of raw event types to format methods. :type event_type_mapping: dict :param event_type_mapping: A mapping of raw event types to more human readable text. :type pretty_dt: :class:`pretty_date_time` :param pretty_dt: An instance of pretty_date_time. """ def __init__(self, config): self.config = config self.event_type_mapping = { 'CommitCommentEvent': 'commented on commit', 'CreateEvent': 'created', 'DeleteEvent': 'deleted', 'FollowEvent': 'followed', 'ForkEvent': 'forked', 'GistEvent': 'created/updated gist', 'GollumEvent': 'created/updated wiki', 'IssueCommentEvent': 'commented on', 'IssuesEvent': '', 'MemberEvent': 'added collaborator', 'MembershipEvent': 'added/removed user', 'PublicEvent': 'open sourced', 'PullRequestEvent': '', 'PullRequestReviewCommentEvent': 'commented on pull request', 'PushEvent': 'pushed to', 'ReleaseEvent': 'released', 'RepositoryEvent': 'created repository', 'WatchEvent': 'starred', } self.event_handlers = { 'CommitCommentEvent': self._format_commit_comment_event, 'CreateEvent': self._format_create_delete_event, 'DeleteEvent': self._format_create_delete_event, 'FollowEvent': self._format_general_event, 'ForkEvent': self._format_fork_event, 'ForkApplyEvent': self._format_general_event, 'GistEvent': self._format_general_event, 'GollumEvent': self._format_general_event, 'IssueCommentEvent': self._format_issue_commment_event, 'IssuesEvent': self._format_issues_event, 'MemberEvent': self._format_general_event, 'MembershipEvent': self._format_general_event, 'PublicEvent': self._format_general_event, 'PullRequestEvent': self._format_pull_request_event, 'PullRequestReviewCommentEvent': self._format_commit_comment_event, 'PushEvent': self._format_push_event, 'ReleaseEvent': self._format_release_event, 'StatusEvent': self._format_general_event, 'TeamAddEvent': self._format_general_event, 'RepositoryEvent': self._format_general_event, 'WatchEvent': self._format_general_event, } self.pretty_dt = pretty_date_time def _format_time(self, event): """Format time. :type event: :class:`github3` Event. :param event: An instance of `github3` Event. """ item = click.style( ' (' + str(self.pretty_dt(event.created_at)) + ')', fg=self.config.clr_time) return item def _format_issue_comment(self, event, key): """Format an issue comment. :type event: :class:`github3` Event. :param event: An instance of `github3` Event. """ issue = '{repo[0]}/{repo[1]}#{num}'.format( repo=event.payload[key].repository, num=event.payload[key].number) return click.style(issue, fg=self.config.clr_tertiary) def _format_indented_message(self, message, newline=True, indent=' ', sha=''): """Format an indented message. :type message: str :param message: The commit comment. :type newline: bool :param newline: Determines whether to prepend a newline. :type indent: str :param indent: The indent, consisting of blank chars. TODO: Consider passing an int denoting # blank chars, or try to calculate the indent dynamically. :type sha: str :param sha: The commit hash. :rtype: str :return: The formattted commit comment. """ subsequent_indent = indent if sha != '': subsequent_indent += ' ' message = self.strip_line_breaks(message) formatted_message = click.wrap_text( text=click.style(sha, fg=self.config.clr_tertiary)+message, initial_indent=indent, subsequent_indent=subsequent_indent) if newline: formatted_message = click.style('\n' + formatted_message) return formatted_message def _format_sha(self, sha): """Format commit hash. :type sha: str :param sha: The commit hash. """ return sha[:7] def _format_commit_comment_event(self, event): """Format commit comment and commit hash. :type event: :class:`github3` Event. :param event: An instance of `github3` Event. """ item = click.style(self.event_type_mapping[event.type] + ' ', fg=self.config.clr_secondary) item += click.style( self._format_sha(event.payload['comment'].commit_id), fg=self.config.clr_tertiary) item += click.style(' at ', fg=self.config.clr_secondary) item += click.style(self.format_user_repo(event.repo), fg=self.config.clr_tertiary) try: item += click.style( '#' + str(event.payload['pull_request'].number) + ' ', fg=self.config.clr_tertiary) except KeyError: pass item += self._format_time(event) try: item += self._format_indented_message( event.payload['pull_request'].title) item += self._format_indented_message( event.payload['comment'].body, indent=' ') except KeyError: item += self._format_indented_message( event.payload['comment'].body) return item def _format_create_delete_event(self, event): """Format a create or delete event. :type event: :class:`github3` Event. :param event: An instance of `github3` Event. """ item = click.style(self.event_type_mapping[event.type], fg=self.config.clr_secondary) item += click.style(' ' + event.payload['ref_type'], fg=self.config.clr_secondary) if event.payload['ref']: item += click.style(' ' + event.payload['ref'], fg=self.config.clr_tertiary) item += click.style(' at ', fg=self.config.clr_secondary) item += click.style(self.format_user_repo(event.repo), fg=self.config.clr_tertiary) item += self._format_time(event) try: item += self._format_indented_message( ('' if event.payload['description'] is None else event.payload['description'])) except KeyError: pass return item def _format_fork_event(self, event): """Format a repo fork event. :type event: :class:`github3` Event. :param event: An instance of `github3` Event. """ item = click.style(self.event_type_mapping[event.type], fg=self.config.clr_secondary) item += click.style(' ' + self.format_user_repo(event.repo), fg=self.config.clr_tertiary) item += self._format_time(event) return item def _format_issue_commment_event(self, event): """Format a repo fork event. :type event: :class:`github3` Event. :param event: An instance of `github3` Event. """ item = click.style(self.event_type_mapping[event.type] + ' ', fg=self.config.clr_secondary) item += self._format_issue_comment(event, key='issue') item += self._format_time(event) item += self._format_indented_message( event.payload['issue'].title) item += self._format_indented_message( event.payload['comment'].body, indent=' ') return item def _format_issues_event(self, event): """Format an issue event. :type event: :class:`github3` Event. :param event: An instance of `github3` Event. """ item = click.style(event.payload['action'] + ' issue ', fg=self.config.clr_secondary) item += self._format_issue_comment(event, key='issue') item += self._format_time(event) item += self._format_indented_message( event.payload['issue'].title) return item def _format_pull_request_event(self, event): """Format a pull request event. :type event: :class:`github3` Event. :param event: An instance of `github3` Event. """ item = click.style(event.payload['action'] + ' pull request ', fg=self.config.clr_secondary) item += self._format_issue_comment(event, key='pull_request') item += self._format_time(event) item += self._format_indented_message( event.payload['pull_request'].title) return item def _format_push_event(self, event): """Format a push event. :type event: :class:`github3` Event. :param event: An instance of `github3` Event. """ item = click.style(self.event_type_mapping[event.type], fg=self.config.clr_secondary) branch = event.payload['ref'].split('/')[-1] item += click.style(' ' + branch, fg=self.config.clr_tertiary) item += click.style(' at ', fg=self.config.clr_secondary) item += click.style(self.format_user_repo(event.repo), fg=self.config.clr_tertiary) item += self._format_time(event) for commit in event.payload['commits']: sha = click.style(self._format_sha(commit['sha']) + ': ', fg=self.config.clr_message) item += self._format_indented_message( commit['message'], sha=sha) return item def _format_release_event(self, event): """Format a release event. :type event: :class:`github3` Event. :param event: An instance of `github3` Event. """ item = click.style(self.event_type_mapping[event.type] + ' ', fg=self.config.clr_secondary) item += click.style(event.payload['release'].tag_name + ' ', fg=self.config.clr_tertiary) item += click.style('at ', fg=self.config.clr_secondary) item += click.style(self.format_user_repo(event.repo), fg=self.config.clr_tertiary) item += self._format_time(event) return item def _format_general_event(self, event): """Format an event, general case used by various event types. :type event: :class:`github3` Event. :param event: An instance of `github3` Event. """ item = click.style(self.event_type_mapping[event.type] + ' ', fg=self.config.clr_secondary) item += click.style(self.format_user_repo(event.repo), fg=self.config.clr_tertiary) item += self._format_time(event) return item def format_email(self, view_entry): """Format an email. :type view_entry: :class:`github3` Email :param view_entry: An instance of `github3` Email. :rtype: str :return: The formattted email. """ email = view_entry.item item = self.format_index_title(view_entry.index, email.email) item += '\n' item += click.style((' ' + 'Primary: ' + str(email.primary).ljust(7) + ' '), fg=self.config.clr_secondary) item += click.style(('Verified: ' + str(email.verified).ljust(5) + ' '), fg=self.config.clr_tertiary) return item def format_emoji(self, view_entry): """Format an emoji. :type view_entry: str :param view_entry: The emoji name. :rtype: str :return: The formattted emoji. """ emoji = view_entry.item item = self.format_index_title(view_entry.index, emoji) return item def format_event(self, view_entry): """Format an event. :type view_entry: :class:`github3` Event :param view_entry: An instance of `github3` Event. :rtype: str :return: The formattted event. """ event = view_entry.item item = self.format_index_title(view_entry.index, str(event.actor)) item += self.event_handlers[event.type](event) return item def format_gitignore_template_name(self, view_entry): """Format a gitignore template name. :type view_entry: str :param view_entry: The gitignore template name. :rtype: str :return: The formattted gitignore template name. """ gitignore_template_name = view_entry.item item = self.format_index_title(view_entry.index, gitignore_template_name) return item def format_feed_entry(self, view_entry): """Format a feed entry. :type view_entry: dict :param view_entry: The URITemplates feed. :rtype: str :return: The formattted feed entry. """ feed_entry = view_entry.item item_parts = feed_entry.title.split(' ') title = item_parts[0] action = item_parts[1:-1] repo = item_parts[-1] item = self.format_index_title(view_entry.index, title) if action[0] == 'forked': item += click.style(action[0] + ' ', fg=self.config.clr_secondary) item += click.style(action[1] + ' ', fg=self.config.clr_tertiary) else: item += click.style(' '.join(action), fg=self.config.clr_secondary) item += click.style(' ' + repo + ' ', fg=self.config.clr_tertiary) item += click.style( '(' + str(self.pretty_dt(feed_entry.updated_parsed)) + ')', fg=self.config.clr_time) if action[0] == 'commented': comment_parts = feed_entry['summary'].split('blockquote') if len(comment_parts) > 2: comment = comment_parts[-2] parts_mention = comment.split('class="user-mention">') if len(parts_mention) > 1: comment = parts_mention[1] comment = self._format_indented_message(comment, newline=False) comment = re.sub(r'(*)', r'', comment) comment = re.sub(r'(

*)', r'', comment) comment = re.sub(r'(

*)', r'', comment) comment = re.sub(r'( *)', r'', comment) item += click.style('\n' + comment, fg=self.config.clr_message) return item def format_license_name(self, view_entry): """Format a license template name. :type view_entry: :class:`github3` License :param view_entry: An instance of `github3` License. :rtype: str :return: The formattted license template name. """ license_template_name = view_entry.item item = self.format_index_title(view_entry.index, license_template_name.key) item += click.style('(' + license_template_name.name + ')', fg=self.config.clr_secondary) return item def format_user(self, view_entry): """Format a user. :type view_entry: :class:`github3` User :param view_entry: An instance of `github3` User. :rtype: str :return: The formattted user. """ user = view_entry.item item = self.format_index_title(view_entry.index, user.login) return item def format_issues_url_from_issue(self, issue): """Format the issue url based on the given issue. :type issue: :class:`github3` Issue :param issue: An instance of `github3` Issue. :rtype: str :return: The formattted issues url. """ return self.format_user_repo(issue.repository) + '/' + \ 'issues/' + str(issue.number) def format_issues_url_from_thread(self, thread): """Format the issue url based on the given thread. :type issue: :class:`github3` Thread :param issue: An instance of `github3` Thread. :rtype: str :return: The formattted issues url. """ url_parts = thread.subject['url'].split('/') user = url_parts[4] repo = url_parts[5] issues_uri = 'issues' issue_id = url_parts[7] return '/'.join([user, repo, issues_uri, issue_id]) def format_index_title(self, index, title): """Format an item's index and title. :type index: str :param index: The index for the given item. :type title: str :param title: The item's title. :rtype: str :return: The formatted index and title. """ formatted_index_title = click.style(' ' + (str(index) + '.').ljust(5), fg=self.config.clr_view_index) formatted_index_title += click.style(title + ' ', fg=self.config.clr_primary) return formatted_index_title def format_issue(self, view_entry): """Format an issue. :type view_entry: :class:`github3` Issue :param view_entry: An instance of `github3` Issue. :rtype: str :return: The formatted issue. """ issue = view_entry.item item = self.format_index_title(view_entry.index, issue.title) item += click.style('@' + str(issue.user) + ' ', fg=self.config.clr_primary) item += click.style(('(' + self.format_issues_url_from_issue(issue) + ')'), fg=self.config.clr_view_link) item += '\n' indent = ' ' if len(item) == 8: item += click.style((' Score: ' + str(item[7]).ljust(10) + ' '), fg=self.config.clr_quaternary) indent = ' ' item += click.style((indent + 'State: ' + str(issue.state).ljust(10) + ' '), fg=self.config.clr_secondary) item += click.style(('Comments: ' + str(issue.comments_count).ljust(5) + ' '), fg=self.config.clr_tertiary) item += click.style(('Assignee: ' + str(issue.assignee).ljust(10) + ' '), fg=self.config.clr_quaternary) return item def format_repo(self, view_entry): """Format a repo. :type view_entry: :class:`github3` Repository :param view_entry: An instance of `github3` Repository. :rtype: str :return: The formatted repo. """ repo = view_entry.item item = self.format_index_title(view_entry.index, repo.full_name) language = repo.language if repo.language is not None else 'Unknown' item += click.style('(' + language + ')', fg=self.config.clr_message) item += '\n' item += click.style((' ' + 'Stars: ' + str(repo.stargazers_count).ljust(6) + ' '), fg=self.config.clr_secondary) item += click.style('Forks: ' + str(repo.forks_count).ljust(6) + ' ', fg=self.config.clr_tertiary) item += click.style(('Updated: ' + str(self.pretty_dt(repo.updated_at)) + ' '), fg=self.config.clr_time) return item def format_thread(self, view_entry): """Format a thread. :type view_entry: :class:`github3` Thread :param view_entry: An instance of `github3` Thread. :rtype: str :return: The formatted thread. """ thread = view_entry.item item = self.format_index_title(view_entry.index, thread.subject['title']) item += click.style('(' + view_entry.url + ')', fg=self.config.clr_view_link) item += '\n' item += click.style((' ' + 'Seen: ' + str(not thread.unread).ljust(7) + ' '), fg=self.config.clr_secondary) item += click.style(('Type: ' + str(thread.subject['type']).ljust(12) + ' '), fg=self.config.clr_tertiary) item += click.style(('Updated: ' + str(self.pretty_dt(thread.updated_at)) + ' '), fg=self.config.clr_time) return item def format_trending_entry(self, view_entry): """Formats a trending repo entry. :type view_entry: dict :param view_entry: The URITemplates feed. :rtype: str :return: The formattted trending entry. """ trending_entry = view_entry.item item_parts = trending_entry.title.split(' ') title = item_parts[0] item = self.format_index_title(view_entry.index, title) try: summary_parts = trending_entry.summary.split('\n') summary = summary_parts[0] if len(summary_parts) > 1 else '' summary = self.strip_line_breaks(summary) language = summary_parts[-1] except AttributeError: summary = '' language = '' if language == '()': language = '(Unknown)' language = re.sub(r'(\()', r'', language) language = re.sub(r'(\))', r'', language) if summary: item += '\n' summary = click.wrap_text( text=summary, initial_indent=' ', subsequent_indent=' ') item += click.style(summary, self.config.clr_message) item += '\n' item += click.style(' ' + language, fg=self.config.clr_message) return item def format_user_repo(self, user_repo_tuple): """Format a repo tuple for pretty print. Example: Input: ('donnemartin', 'gitsome') Output: donnemartin/gitsome Input: ('repos/donnemartin', 'gitsome') Output: donnemartin/gitsome :type user_repo_tuple: tuple :param user_repo_tuple: The user and repo. :rtype: str :return: A string of the form user/repo. """ result = '/'.join(user_repo_tuple) if result.startswith('repos/'): return result[len('repos/'):] return result def strip_line_breaks(self, text): """Strips \r and \n characters. These characters seem to cause issues with `click.wrap_text`. :type text: str :param text: The text to strip of line breaks. :rtype: str :return: The input text without line breaks. """ text = re.sub(r'\r', '', text) text = re.sub(r'\n', ' ', text) return text gitsome-0.8.0/gitsome/github.py000066400000000000000000001024621345243314000164750ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2015 Donne Martin. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from __future__ import unicode_literals from __future__ import print_function import os import platform import sys import urllib import webbrowser from .lib.github3 import null from .lib.github3.exceptions import AuthenticationFailed, UnprocessableEntity from .lib.img2txt import img2txt import click import feedparser from requests.exceptions import MissingSchema, SSLError from .config import Config from .formatter import Formatter from .rss_feed import language_rss_map from .table import Table from .view_entry import ViewEntry from .web_viewer import WebViewer from .utils import TextUtils class GitHub(object): """Provide integration with the GitHub API. :type config: :class:`config.Config` :param config: An instance of `config.Config`. :type formatter: :class:`formatter.Formatter` :param formatter: An instance of `formatter.Formatter`. :type img2txt: callable :param img2txt: A callable fom img2txt. :type table: :class:`table.Table` :param table: An instance of `table.Table`. :type trend_parser: :class:`feedparser` :param trend_parser: An instance of `feedparser`. :type web_viewer: :class:`web_viewer.WebViewer` :param web_viewer: An instance of `web_viewer.WebViewer`. :type _base_url: str :param _base_url: The base GitHub or GitHub Enterprise url. """ def __init__(self): self.config = Config() self.formatter = Formatter(self.config) self.img2txt = img2txt.img2txt self.table = Table(self.config) self.web_viewer = WebViewer(self.config) self.trend_parser = feedparser self.text_utils = TextUtils() self._base_url = 'https://github.com/' @property def base_url(self): return self.config.enterprise_url or self._base_url def add_base_url(self, url): """Add the base url if it is not already part of the given url. :type url: str :param url: The url. :return: The url including the base url. """ return self.base_url + url if self.base_url not in url else url def authenticate(func): """Decorator that authenticates credentials. :type func: callable :param func: A method to execute if authorization passes. :return: The return value of `func` if authorization passes, or None if authorization fails. """ def auth_wrapper(self, *args, **kwargs): self.config.authenticate() self.config.save_config() if self.config.check_auth(): try: return func(self, *args, **kwargs) except SSLError: click.secho(('SSL cert verification failed.\n Try running ' 'gh configure --enterprise\n and type ' "'n' when asked whether to verify SSL certs."), fg=self.config.clr_error) except MissingSchema: click.secho('Invalid GitHub Enterprise url', fg=self.config.clr_error) except AuthenticationFailed: self.config.print_auth_error() return auth_wrapper def avatar(self, url, text_avatar): """Display the user's avatar from the specified url. This method requires PIL. :type url: str :param url: The user's avatar image. :type text_avatar: bool :param text_avatar: Determines whether to view the profile avatar in plain text (True) or in ansi (False). On Windows this value is always set to True due to lack of support of `img2txt` on Windows. :rtype: str :return: The avatar. """ if platform.system() == 'Windows': text_avatar = True avatar = self.config.get_github_config_path( self.config.CONFIG_AVATAR) try: urllib.request.urlretrieve(url, avatar) except urllib.error.URLError: pass avatar_text = '' if os.path.exists(avatar): avatar_text = self.img2txt(avatar, ansi=(not text_avatar)) avatar_text += '\n' os.remove(avatar) return avatar_text def avatar_setup(self, url, text_avatar): """Prepare to display the user's avatar from the specified url. This method requires PIL. :type url: str :param url: The user's avatar image. :type text_avatar: bool :param text_avatar: Determines whether to view the profile avatar in plain text (True) or in ansi (False). On Windows this value is always set to True due to lack of support of `img2txt` on Windows. :rtype: str :return: The avatar. """ try: import PIL # NOQA return self.avatar(url, text_avatar) except ImportError: avatar_text = click.style(('To view the avatar in your terminal, ' 'install the Python Image Library.\n'), fg=self.config.clr_message) return avatar_text def configure(self, enterprise): """Configure gitsome. Attempts to authenticate the user and to set up the user's news feed. If `gitsome` has not yet been configured, calling a `gh` command that requires authentication will automatically invoke the `configure` command. :type enterprise: bool :param enterprise: Determines whether to configure GitHub Enterprise. """ self.config.authenticate(enterprise=enterprise, overwrite=True) self.config.prompt_news_feed() self.config.show_bash_completions_info() self.config.save_config() @authenticate def create_comment(self, user_repo_number, text): """Create a comment on the given issue. :type user_repo_number: str :param user_repo_number: The user/repo/issue_number. :type text: str :param text: The comment text. """ try: user, repo, number = user_repo_number.split('/') int(number) # Check for int except ValueError: click.secho(('Expected argument: user/repo/# and option -t ' '"comment".'), fg=self.config.clr_error) return issue = self.config.api.issue(user, repo, number) issue_comment = issue.create_comment(text) if type(issue_comment) is not null.NullObject: click.secho('Created comment: ' + issue_comment.body, fg=self.config.clr_message) else: click.secho('Error creating comment', fg=self.config.clr_error) @authenticate def create_issue(self, user_repo, issue_title, issue_desc=''): """Create an issue. :type user_repo: str :param user_repo: The user/repo. :type issue_title: str :param issue_title: The issue title. :type issue_desc: str :param issue_desc: The issue body (optional). """ try: user, repo_name = user_repo.split('/') except ValueError: click.secho('Expected argument: user/repo and option -t "title".', fg=self.config.clr_error) return issue = self.config.api.create_issue(user, repo_name, issue_title, issue_desc) if type(issue) is not null.NullObject: body = self.text_utils.sanitize_if_none(issue.body) click.secho('Created issue: ' + issue.title + '\n' + body, fg=self.config.clr_message) else: click.secho('Error creating issue.', fg=self.config.clr_error) @authenticate def create_repo(self, repo_name, repo_desc='', private=False): """Create a repo. :type repo_name: str :param repo_name: The repo name. :type repo_desc: str :param repo_desc: The repo description (optional). :type private: bool :param private: Determines whether the repo is private. Default: False. """ try: repo = self.config.api.create_repository(repo_name, repo_desc, private=private) desc = self.text_utils.sanitize_if_none(repo.description) click.secho(('Created repo: ' + repo.full_name + '\n' + desc), fg=self.config.clr_message) except UnprocessableEntity as e: click.secho('Error creating repo: ' + str(e.msg), fg=self.config.clr_error) @authenticate def emails(self): """List all the user's registered emails.""" self.table.build_table_setup(self.config.api.emails(), self.formatter.format_email, limit=sys.maxsize, pager=False, build_urls=False) @authenticate def emojis(self, pager=False): """List all GitHub supported emojis. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ self.table.build_table_setup(self.config.api.emojis(), self.formatter.format_emoji, limit=sys.maxsize, pager=pager, build_urls=False) @authenticate def feed(self, user_or_repo='', private=False, pager=False): """List all activity for the given user or repo. If `user_or_repo` is not provided, uses the logged in user's news feed seen while visiting https://github.com. If `user_or_repo` is provided, shows either the public or `[-p/--private]` feed activity of the user or repo. :type user_or_repo: str :param user_or_repo: The user or repo to list events for (optional). If no entry, defaults to the logged in user's feed. :type private: bool :param private: Determines whether to show the private events (True) or public events (False). :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ click.secho('Listing events...', fg=self.config.clr_message) if user_or_repo == '': if self.config.user_feed is None: self.config.prompt_news_feed() self.config.save_config() if self.config.user_feed: items = self.trend_parser.parse(self.config.user_feed) self.table.build_table_setup_feed( items, self.formatter.format_feed_entry, pager) else: if '/' in user_or_repo: user, repo = user_or_repo.split('/') repo = self.config.api.repository(user, repo) items = repo.events() else: public = False if private else True items = self.config.api.user(user_or_repo).events(public=public) self.table.build_table_setup( items, self.formatter.format_event, limit=sys.maxsize, pager=pager, build_urls=False) @authenticate def followers(self, user, pager=False): """List all followers and the total follower count. :type user: str :param user: The user login (optional). If None, returns the followers of the logged in user. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ if user is None: user = self.config.user_login self.table.build_table_setup_user( self.config.api.followers_of(user), self.formatter.format_user, limit=sys.maxsize, pager=pager) @authenticate def following(self, user, pager=False): """List all followed users and the total followed count. :type user: str :param user: The user login. If None, returns the followed users of the logged in user. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ if user is None: user = self.config.user_login self.table.build_table_setup_user( self.config.api.followed_by(user), self.formatter.format_user, limit=sys.maxsize, pager=pager) @authenticate def gitignore_template(self, language): """Output the gitignore template for the given language. :type language: str :param language: The language. """ template = self.config.api.gitignore_template(language) if template: click.secho(template, fg=self.config.clr_message) else: click.secho(('Invalid case-sensitive template requested, run the ' 'following command to see available templates:\n' ' gh gitignore-templates'), fg=self.config.clr_error) @authenticate def gitignore_templates(self, pager=False): """Output all supported gitignore templates. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ self.table.build_table_setup( self.config.api.gitignore_templates(), self.formatter.format_gitignore_template_name, limit=sys.maxsize, pager=pager, build_urls=False) click.secho((' Run the following command to view or download a ' '.gitignore file:\n' ' View: gh gitignore Python\n' ' Download: gh gitignore Python > .gitignore\n'), fg=self.config.clr_message) @authenticate def issue(self, user_repo_number): """Output detailed information about the given issue. :type user_repo_number: str :param user_repo_number: The user/repo/issue_number. """ try: user, repo, number = user_repo_number.split('/') int(number) # Check for int except ValueError: click.secho('Expected argument: user/repo/#.', fg=self.config.clr_error) return url = (self.base_url + user + '/' + repo + '/' + 'issues/' + number) self.web_viewer.view_url(url) @authenticate def issues(self, issues_list, limit=1000, pager=False, sort=True): """List all issues. :type issues_list: list :param issues_list: A list of `github3` Issues. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. :type sort: bool :param sort: Determines whether to sort the issues by: state, repo, created_at. """ view_entries = [] for current_issue in issues_list: url = self.formatter.format_issues_url_from_issue(current_issue) view_entries.append( ViewEntry( current_issue, url=url, sort_key_primary=current_issue.state, sort_key_secondary=current_issue.repository, sort_key_tertiary=current_issue.created_at)) if sort: view_entries = sorted(view_entries, reverse=False) self.table.build_table(view_entries, limit, pager, self.formatter.format_issue) @authenticate def issues_setup(self, issue_filter='subscribed', issue_state='open', limit=1000, pager=False): """Prepare to list all issues matching the filter. :type issue_filter: str :param issue_filter: 'assigned', 'created', 'mentioned', 'subscribed' (default). :type issue_state: str :param issue_state: 'all', 'open' (default), 'closed'. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ self.issues(self.config.api.issues(issue_filter, issue_state), limit, pager) @authenticate def license(self, license_name): """Output the gitignore template for the given language. :type license_name: str :param license_name: The license name. """ result = self.config.api.license(license_name) if type(result) is not null.NullObject: click.secho(result.body, fg=self.config.clr_message) else: click.secho((' Invalid case-sensitive license requested, run the ' 'following command to see available licenses:\n' ' gh licenses'), fg=self.config.clr_error) @authenticate def licenses(self): """Output the gitignore template for the given language.""" self.table.build_table_setup( self.config.api.licenses(), self.formatter.format_license_name, limit=sys.maxsize, pager=False, build_urls=False) click.secho((' Run the following command to view or download a ' 'LICENSE file:\n' ' gh license apache-2.0\n' ' gh license apache-2.0 > LICENSE\n'), fg=self.config.clr_message) @authenticate def notifications(self, limit=1000, pager=False): """List all notifications. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ view_entries = [] for thread in self.config.api.notifications(all=True, participating=False): url = self.formatter.format_issues_url_from_thread(thread) view_entries.append(ViewEntry(thread, url=url)) self.table.build_table(view_entries, limit, pager, self.formatter.format_thread) @authenticate def octocat(self, say): """Output an Easter egg or the given message from Octocat. :type say: str :param say: What Octocat should say. If say is None, octocat speaks an Easter egg. """ output = str(self.config.api.octocat(say)) output = output.replace('\\n', '\n') click.secho(output, fg=self.config.clr_message) @authenticate def pull_requests(self, limit=1000, pager=False): """List all pull requests. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ issues_list = [] repositories = self.config.api.repositories() for repository in repositories: repo_pulls = repository.pull_requests() for repo_pull in repo_pulls: url = self.formatter.format_issues_url_from_issue(repo_pull) user, repo, issues, number = url.split('/') repo_pull = self.config.api.pull_request(user, repo, number) issues_list.append(repo_pull) self.issues(issues_list, limit, pager) @authenticate def rate_limit(self): """Output the rate limit. Not available for GitHub Enterprise. Logged in users can make 5000 requests per hour. See: https://developer.github.com/v3/#rate-limiting """ click.secho('Rate limit: ' + str(self.config.api.ratelimit_remaining), fg=self.config.clr_message) @authenticate def repositories(self, repos, limit=1000, pager=False, repo_filter='', print_output=True, sort=True): """List all repos matching the given filter. :type repos: list :param repos: A list of `github3` Repository. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. :type repo_filter: str :param repo_filter: The filter for repo names. Only repos matching the filter will be returned. If None, outputs all repos. :type print_output: bool :param print_output: Determines whether to print the output (True) or return the output as a string (False). :type sort: bool :param sort: Determines whether to sort the issues by: state, repo, created_at. :rtype: str :return: The output if print_output is True else, returns None. """ view_entries = [] for repo in repos: url = repo.clone_url if (repo.full_name is not None and repo_filter in repo.full_name.lower()) or \ (repo.description is not None and repo_filter in repo.description.lower()): view_entries.append( ViewEntry(repo, url=url, sort_key_primary=repo.stargazers_count)) if sort: view_entries = sorted(view_entries, reverse=True) return self.table.build_table(view_entries, limit, pager, self.formatter.format_repo, print_output=print_output) @authenticate def repositories_setup(self, repo_filter, limit=1000, pager=False): """Prepare to list all repos matching the given filter. :type repo_filter: str :param repo_filter: The filter for repo names. Only repos matching the filter will be returned. If None, outputs all repos retrieved by the GitHub API. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ self.repositories(self.config.api.repositories(), limit, pager, repo_filter) @authenticate def repository(self, user_repo): """Output detailed information about the given repo. :type user_repo: str :param user_repo: The user/repo. """ try: user, repo = user_repo.split('/') except ValueError: click.secho('Expected argument: user/repo.', fg=self.config.clr_error) return self.web_viewer.view_url(self.base_url + user_repo) @authenticate def search_issues(self, query, limit=1000, pager=False): """Search for all issues matching the given query. :type query: str :param query: The search query. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ click.secho('Searching for all matching issues on GitHub...', fg=self.config.clr_message) results = self.config.api.search_issues(query) issues_list = [] for result in results: issues_list.append(result.issue) self.issues(issues_list, limit, pager, sort=False) @authenticate def search_repositories(self, query, sort, limit=1000, pager=False): """Search for all repos matching the given query. :type query: str :param query: The search query. :type sort: str :param sort: Optional: 'stars', 'forks', 'updated'. If not specified, sorting is done by query best match. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ click.secho('Searching for all matching repos on GitHub...', fg=self.config.clr_message) results = self.config.api.search_repositories(query, sort) repos = [] for result in results: repos.append(result.repository) self.repositories(repos, limit, pager, sort=False) @authenticate def starred(self, repo_filter, limit=1000, pager=False): """Output starred repos. :type repo_filter: str :param repo_filter: The filter for repo names. Only repos matching the filter will be returned. If None, outputs all starred repos. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ self.repositories(self.config.api.starred(), limit, pager, repo_filter.lower()) def trending(self, language, weekly, monthly, devs=False, browser=False, pager=False): """List trending repos for the given language. :type language: str :param language: The language (optional). If blank, shows 'Overall'. :type weekly: bool :param weekly: Determines whether to show the weekly rankings. Daily is the default. :type monthly: bool :param monthly: Determines whether to show the monthly rankings. Daily is the default. If both `monthly` and `weekly` are set, `monthly` takes precedence. :type devs: bool :param devs: determines whether to display the trending devs or repos. Only valid with the -b/--browser option. :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ language = language.lower() if language in language_rss_map: language = language_rss_map[language] if monthly: period = 'monthly' url_param = '?since=monthly' elif weekly: period = 'weekly' url_param = '?since=weekly' else: period = 'daily' url_param = '' if browser: webbrowser.open( ('https://github.com/trending' + ('/developers' if devs else '') + ('/' + language if language is not 'overall' else '') + url_param)) else: click.secho( 'Listing {p} trending {l} repos...'.format(l=language, p=period), fg=self.config.clr_message) url = ('http://github-trends.ryotarai.info/rss/github_trends_' + language + '_') url += period + '.rss' items = self.trend_parser.parse(url) self.table.build_table_setup_trending( items.entries, self.formatter.format_trending_entry, limit=sys.maxsize, pager=pager) @authenticate def user(self, user_id, browser=False, text_avatar=False, limit=1000, pager=False): """List information about the logged in user. :type user_id: str :param user_id: The user id/login. If None, returns followers of the logged in user. :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. :type text_avatar: bool :param text_avatar: Determines whether to view the profile avatar in plain text instead of ansi (default). On Windows this value is always set to True due to lack of support of `img2txt` on Windows. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ if browser: webbrowser.open(self.base_url + user_id) else: user = self.config.api.user(user_id) if type(user) is null.NullObject: click.secho('Invalid user.', fg=self.config.clr_error) return output = '' output += click.style(self.avatar_setup(user.avatar_url, text_avatar)) output += click.style(user.login + '\n', fg=self.config.clr_primary) if user.company is not None: output += click.style(user.company + '\n', fg=self.config.clr_secondary) if user.location is not None: output += click.style(user.location + '\n', fg=self.config.clr_secondary) if user.email is not None: output += click.style(user.email + '\n', fg=self.config.clr_secondary) if user.type == 'Organization': output += click.style('Organization\n\n', fg=self.config.clr_tertiary) else: output += click.style( 'Followers: ' + str(user.followers_count) + ' | ', fg=self.config.clr_tertiary) output += click.style( 'Following: ' + str(user.following_count) + '\n\n', fg=self.config.clr_tertiary) output += self.repositories(self.config.api.repositories(user_id), limit, pager, print_output=False) if pager: color = None if platform.system() == 'Windows': color = True click.echo_via_pager(output, color) else: click.secho(output) @authenticate def user_me(self, browser, text_avatar, limit=1000, pager=False): """List information about the logged in user. :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. :type text_avatar: bool :param text_avatar: Determines whether to view the profile avatar in plain text. On Windows this value is always set to True due to lack of support of `img2txt` on Windows. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ self.user(self.config.user_login, browser, text_avatar, limit, pager) @authenticate def view(self, index, view_in_browser=False): """View the given index in a browser. Load urls from ~/.gitsomeconfigurl and stores them in self.config.urls. Open a browser with the url based on the given index. :type index: int :param index: Determines the index to view. :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. """ self.config.urls = self.config.load_urls(view_in_browser) url = self.config.urls[index-1] click.secho('Viewing ' + url + '...', fg=self.config.clr_message) if view_in_browser: webbrowser.open(self.add_base_url(url)) else: if 'issues/' in url: url = url.replace('issues/', '') self.issue(url) elif len(url.split('/')) == 2: self.repository(url) else: self.web_viewer.view_url(self.add_base_url(url)) gitsome-0.8.0/gitsome/githubcli.py000066400000000000000000000736241345243314000171740ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2015 Donne Martin. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from __future__ import unicode_literals from __future__ import print_function import click from .github import GitHub click.disable_unicode_literals_warning = True pass_github = click.make_pass_decorator(GitHub) class GitHubCli(object): """The GitHubCli, builds `click` commands and runs `GitHub` methods.""" @click.group() @click.pass_context def cli(ctx): """Main entry point for GitHubCli. :type ctx: :class:`click.core.Context` :param ctx: An instance of click.core.Context that stores an instance of `github.GitHub`. """ # Create a GitHub object and remember it as the context object. # From this point onwards other commands can refer to it by using the # @pass_github decorator. ctx.obj = GitHub() @cli.command() @click.option('-e', '--enterprise', is_flag=True) @pass_github def configure(github, enterprise): """Configure gitsome. Attempts to authenticate the user and to set up the user's news feed. Usage/Example(s): gh configure gh configure -e gh configure --enterprise :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type enterprise: bool :param enterprise: Determines whether to configure GitHub Enterprise. Default: False. """ github.configure(enterprise) @cli.command('create-comment') @click.argument('user_repo_number') @click.option('-t', '--text') @pass_github def create_comment(github, user_repo_number, text): """Create a comment on the given issue. Usage: gh create-comment [user_repo_number] [-t/--text] Example(s): gh create-comment donnemartin/saws/1 -t "hello world" gh create-comment donnemartin/saws/1 --text "hello world" :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type user_repo_number: str :param user_repo_number: The user/repo/issue_number. :type text: str :param text: The comment text. """ github.create_comment(user_repo_number, text) @cli.command('create-issue') @click.argument('user_repo') @click.option('-t', '--issue_title') @click.option('-d', '--issue_desc', required=False) @pass_github def create_issue(github, user_repo, issue_title, issue_desc): """Create an issue. Usage: gh create-issue [user_repo] [-t/--issue_title] [-d/--issue_desc] Example(s): gh create-issue donnemartin/gitsome -t "title" gh create-issue donnemartin/gitsome -t "title" -d "desc" gh create-issue donnemartin/gitsome --issue_title "title" --issue_desc "desc" # NOQA :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type user_repo: str :param user_repo: The user/repo. :type issue_title: str :param issue_title: The issue title. :type issue_desc: str :param issue_desc: The issue body (optional). """ github.create_issue(user_repo, issue_title, issue_desc) @cli.command('create-repo') @click.argument('repo_name') @click.option('-d', '--repo_desc', required=False) @click.option('-pr', '--private', is_flag=True) @pass_github def create_repo(github, repo_name, repo_desc, private): """Create a repo. Usage: gh create-repo [repo_name] [-d/--repo_desc] [-pr/--private] Example(s): gh create-repo repo_name gh create-repo repo_name -d "desc" gh create-repo repo_name --repo_desc "desc" gh create-repo repo_name -pr gh create-repo repo_name --repo_desc "desc" --private :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type repo_name: str :param repo_name: The repo name. :type repo_desc: str :param repo_desc: The repo description (optional). :type private: bool :param private: Determines whether the repo is private. Default: False. """ github.create_repo(repo_name, repo_desc, private) @cli.command() @pass_github def emails(github): """List all the user's registered emails. Usage/Example(s): gh emails :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. """ github.emails() @cli.command() @click.option('-p', '--pager', is_flag=True) @pass_github def emojis(github, pager): """List all GitHub supported emojis. Usage: gh emojis [-p/--pager] Example(s): gh emojis | grep octo :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.emojis(pager) @cli.command() @click.argument('user_or_repo', required=False, default='') @click.option('-pr', '--private', is_flag=True, default=False) @click.option('-p', '--pager', is_flag=True) @pass_github def feed(github, user_or_repo, private, pager): """List all activity for the given user or repo. If `user_or_repo` is not provided, uses the logged in user's news feed seen while visiting https://github.com. If `user_or_repo` is provided, shows either the public or `[-pr/--private]` feed activity of the user or repo. Usage: gh feed [user_or_repo] [-pr/--private] [-p/--pager] Examples: gh feed gh feed | grep foo gh feed donnemartin gh feed donnemartin -pr -p gh feed donnemartin --private --pager gh feed donnemartin/haxor-news -p :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type user_or_repo: str :param user_or_repo: The user or repo to list events for (optional). If no entry, defaults to the logged in user's feed. :type private: bool :param private: Determines whether to show the private events (True) or public events (False). :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.feed(user_or_repo, private, pager) @cli.command() @click.argument('user', required=False) @click.option('-p', '--pager', is_flag=True) @pass_github def followers(github, user, pager): """List all followers and the total follower count. Usage: gh followers [user] [-p/--pager] Example(s): gh followers gh followers -p gh followers octocat --pager :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type user: str :param user: The user login (optional). If None, returns the followers of the logged in user. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.followers(user, pager) @cli.command() @click.argument('user', required=False) @click.option('-p', '--pager', is_flag=True) @pass_github def following(github, user, pager): """List all followed users and the total followed count. Usage: gh following [user] [-p/--pager] Example(s): gh following gh following -p gh following octocat --pager :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type user: str :param user: The user login. If None, returns the followed users of the logged in user. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.following(user, pager) @cli.command('gitignore-template') @click.argument('language') @pass_github def gitignore_template(github, language): """Output the gitignore template for the given language. Usage: gh gitignore-template [language] Example(s): gh gitignore-template Python gh gitignore-template Python > .gitignore :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type language: str :param language: The language. """ github.gitignore_template(language) @cli.command('gitignore-templates') @click.option('-p', '--pager', is_flag=True) @pass_github def gitignore_templates(github, pager): """Output all supported gitignore templates. Usage: gh gitignore-templates Example(s): gh gitignore-templates gh gitignore-templates -p gh gitignore-templates --pager :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.gitignore_templates(pager) @cli.command() @click.argument('user_repo_number') @pass_github def issue(github, user_repo_number): """Output detailed information about the given issue. Usage: gh issue [user_repo_number] Example(s): gh issue donnemartin/saws/1 :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type user_repo_number: str :param user_repo_number: The user/repo/issue_number. """ github.issue(user_repo_number) @cli.command() @click.option('-f', '--issue_filter', required=False, default='subscribed') @click.option('-s', '--issue_state', required=False, default='open') @click.option('-l', '--limit', required=False, default=1000) @click.option('-p', '--pager', is_flag=True) @pass_github def issues(github, issue_filter, issue_state, limit, pager): """List all issues matching the filter. Usage: gh issues [-f/--issue_filter] [-s/--issue_state] [-l/--limit] [-p/--pager] # NOQA Example(s): gh issues gh issues -f assigned gh issues ---issue_filter created gh issues -s all -l 20 -p gh issues --issue_state closed --limit 20 --pager gh issues -f created -s all -p :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type issue_filter: str :param issue_filter: assigned, created, mentioned, subscribed (default). :type issue_state: str :param issue_state: all, open (default), closed. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.issues_setup(issue_filter, issue_state, limit, pager) @cli.command() @click.argument('license_name') @pass_github def license(github, license_name): """Output the license template for the given license. Usage: gh license [license_name] Example(s): gh license apache-2.0 gh license mit > LICENSE :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type license_name: str :param license_name: The license name. """ github.license(license_name) @cli.command() @pass_github def licenses(github): """Output all supported license templates. Usage/Example(s): gh licenses :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. """ github.licenses() @cli.command() @click.option('-b', '--browser', is_flag=True) @click.option('-t', '--text_avatar', is_flag=True) @click.option('-l', '--limit', required=False, default=1000) @click.option('-p', '--pager', is_flag=True) @pass_github def me(github, browser, text_avatar, limit, pager): """List information about the logged in user. Usage: gh me [-b/--browser] [-t/--text_avatar] [-l/--limit] [-p/--pager] Example(s): gh me gh me -b gh me --browser gh me -t -l 20 -p gh me --text_avatar --limit 20 --pager :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. :type text_avatar: bool :param text_avatar: Determines whether to view the profile avatar in plain text instead of ansi (default). On Windows this value is always set to True due to lack of support of `img2txt` on Windows. :type limit: int :param limit: The number of user repos to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.user_me(browser, text_avatar, limit, pager) @cli.command() @click.option('-l', '--limit', required=False, default=1000) @click.option('-p', '--pager', is_flag=True) @pass_github def notifications(github, limit, pager): """List all notifications. Usage: gh notifications [-l/--limit] [-p/--pager] Example(s): gh notifications gh notifications -l 20 -p gh notifications --limit 20 --pager :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.notifications(limit, pager) @cli.command('octo') @click.argument('say', required=False) @pass_github def octocat(github, say): """Output an Easter egg or the given message from Octocat. Usage: gh octo [say] Example(s): gh octo gh octo "foo bar" :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type say: str :param say: What Octocat should say. If say is None, octocat speaks an Easter egg. """ github.octocat(say) @cli.command('pull-request') @click.argument('user_repo_number') @pass_github def pull_request(github, user_repo_number): """Output detailed information about the given pull request. Usage: gh pull-request [user_repo_number] Example(s): gh pull-request donnemartin/saws/80 :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type user_repo_number: str :param user_repo_number: The user/repo/pull_number. """ github.issue(user_repo_number) @cli.command('pull-requests') @click.option('-l', '--limit', required=False, default=1000) @click.option('-p', '--pager', is_flag=True) @pass_github def pull_requests(github, limit, pager): """List all pull requests. Usage: gh pull-requests [-l/--limit] [-p/--pager] Example(s): gh pull-requests gh pull-requests -l 20 -p gh pull-requests --limit 20 --pager :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.pull_requests(limit, pager) @cli.command('rate-limit') @pass_github def rate_limit(github): """Output the rate limit. Not available for GitHub Enterprise. Logged in users can make 5000 requests per hour. See: https://developer.github.com/v3/#rate-limiting Usage/Example(s): gh rate-limit :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. """ github.rate_limit() @cli.command('repos') @click.argument('repo_filter', required=False, default='') @click.option('-l', '--limit', required=False, default=1000) @click.option('-p', '--pager', is_flag=True) @pass_github def repositories(github, repo_filter, limit, pager): """List all repos matching the given filter. Usage: gh repos [repo_filter] [-l/--limit] [-p/--pager] Example(s): gh repos gh repos "data-science" gh repos "data-science" -l 20 -p gh repos "data-science" --limit 20 --pager :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type repo_filter: str :param repo_filter: The filter for repo names. Only repos matching the filter will be returned. If None, outputs all the logged in user's repos. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.repositories_setup(repo_filter, limit, pager) @cli.command('repo') @click.argument('user_repo') @pass_github def repository(github, user_repo): """Output detailed information about the given repo. Usage: gh repo [user_repo] Example(s): gh repo donnemartin/gitsome :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type user_repo: str :param user_repo: The user/repo. """ github.repository(user_repo) @cli.command('search-issues') @click.argument('query') @click.option('-l', '--limit', required=False, default=1000) @click.option('-p', '--pager', is_flag=True) @pass_github def search_issues(github, query, limit, pager): """Search for all issues matching the given query. Usage: gh search-issues [query] [-l/--limit] [-p/--pager] Example(s): gh search-issues "foo type:pr author:donnemartin" -l 20 -p gh search-issues "foobarbaz in:title created:>=2015-01-01" --limit 20 --pager # NOQA Additional Example(s): Search issues that have your user name tagged @donnemartin: gh search-issues "is:issue donnemartin is:open" -p Search issues that have the most +1s: gh search-issues "is:open is:issue sort:reactions-+1-desc" -p Search issues that have the most comments: gh search-issues "is:open is:issue sort:comments-desc" -p Search issues with the "help wanted" tag: gh search-issues "is:open is:issue label:\"help wanted\"" -p Search all your open private issues: gh search-issues "is:open is:issue is:private" -p The query can contain any combination of the following supported qualifers: - `type` With this qualifier you can restrict the search to issues or pull request only. - `in` Qualifies which fields are searched. With this qualifier you can restrict the search to just the title, body, comments, or any combination of these. - `author` Finds issues created by a certain user. - `assignee` Finds issues that are assigned to a certain user. - `mentions` Finds issues that mention a certain user. - `commenter` Finds issues that a certain user commented on. - `involves` Finds issues that were either created by a certain user, assigned to that user, mention that user, or were commented on by that user. - `state` Filter issues based on whether they’re open or closed. - `labels` Filters issues based on their labels. - `language` Searches for issues within repositories that match a certain language. - `created` or `updated` Filters issues based on times of creation, or when they were last updated. - `comments` Filters issues based on the quantity of comments. - `user` or `repo` Limits searches to a specific user or repository. For more information about these qualifiers, see: http://git.io/d1oELA :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type query: str :param query: The search query. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.search_issues(query, limit, pager) @cli.command('search-repos') @click.argument('query') @click.option('-s', '--sort', required=False, default='') @click.option('-l', '--limit', required=False, default=1000) @click.option('-p', '--pager', is_flag=True) @pass_github def search_repositories(github, query, sort, limit, pager): """Search for all repos matching the given query. Usage: gh search-repos [query] [-s/--sort] [-l/--limit] [-p/--pager] Example(s): gh search-repos "maps language:python" -s stars -l 20 -p gh search-repos "created:>=2015-01-01 stars:>=1000 language:python" --sort stars --limit 20 --pager # NOQA The query can contain any combination of the following supported qualifers: - `in` Qualifies which fields are searched. With this qualifier you can restrict the search to just the repository name, description, readme, or any combination of these. - `size` Finds repositories that match a certain size (in kilobytes). - `forks` Filters repositories based on the number of forks, and/or whether forked repositories should be included in the results at all. - `created` or `pushed` Filters repositories based on times of creation, or when they were last updated. Format: `YYYY-MM-DD`. Examples: `created:<2011`, `pushed:<2013-02`, `pushed:>=2013-03-06` - `user` or `repo` Limits searches to a specific user or repository. - `language` Searches repositories based on the language they're written in. - `stars` Searches repositories based on the number of stars. For more information about these qualifiers, see: http://git.io/4Z8AkA :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type query: str :param query: The search query. :type sort: str :param sort: Optional: 'stars', 'forks', 'updated'. If not specified, sorting is done by query best match. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.search_repositories(query, sort, limit, pager) @cli.command() @click.argument('repo_filter', required=False, default='') @click.option('-l', '--limit', required=False, default=1000) @click.option('-p', '--pager', is_flag=True) @pass_github def starred(github, repo_filter, limit, pager): """Output starred repos. Usage: gh starred [repo_filter] [-l/--limit] [-p/--pager] Example(s): gh starred gh starred foo -l 20 -p gh starred foo --limit 20 --pager :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type repo_filter: str :param repo_filter: The filter for repo names. Only repos matching the filter will be returned. If None, outputs all starred repos. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.starred(repo_filter, limit, pager) @cli.command() @click.argument('language', required=False, default='Overall') @click.option('-w', '--weekly', is_flag=True) @click.option('-m', '--monthly', is_flag=True) @click.option('-D', '--devs', is_flag=True) @click.option('-b', '--browser', is_flag=True) @click.option('-p', '--pager', is_flag=True) @pass_github def trending(github, language, weekly, monthly, devs, browser, pager): """List trending repos for the given language. Usage: gh trending [language] [-w/--weekly] [-m/--monthly] [-D/--devs] [-b/--browser] [-p/--pager] # NOQA Example(s): gh trending gh trending Python -w -p gh trending Python --weekly --devs --browser gh trending --browser :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type language: str :param language: The language (optional). If blank, shows 'Overall'. :type weekly: bool :param weekly: Determines whether to show the weekly rankings. Daily is the default. :type monthly: bool :param monthly: Determines whether to show the monthly rankings. Daily is the default. If both `monthly` and `weekly` are set, `monthly` takes precedence. :type devs: bool :param devs: determines whether to display the trending devs or repos. Only valid with the -b/--browser option. :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.trending(language, weekly, monthly, devs, browser, pager) @cli.command() @click.argument('user_id', required=True) @click.option('-b', '--browser', is_flag=True) @click.option('-t', '--text_avatar', is_flag=True) @click.option('-l', '--limit', required=False, default=1000) @click.option('-p', '--pager', is_flag=True) @pass_github def user(github, user_id, browser, text_avatar, limit, pager): """List information about the given user. Usage: gh user [user_id] [-b/--browser] [-t/--text_avatar] [-l/--limit] [-p/--pager] # NOQA Example(s): gh user octocat gh user octocat -b gh user octocat --browser gh user octocat -t -l 10 -p gh user octocat --text_avatar --limit 10 --pager :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type user_id: str :param user_id: The user id/login. If None, returns followers of the logged in user. :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. :type text_avatar: bool :param text_avatar: Determines whether to view the profile avatar in plain text instead of ansi (default). On Windows this value is always set to True due to lack of support of `img2txt` on Windows. :type limit: int :param limit: The number of items to display. :type pager: bool :param pager: Determines whether to show the output in a pager, if available. """ github.user(user_id, browser, text_avatar, limit, pager) @cli.command() @click.argument('index') @click.option('-b', '--browser', is_flag=True) @pass_github def view(github, index, browser): """View the given notification/repo/issue/pull_request/user index. This method is meant to be called after one of the following commands which outputs a table of notifications/repos/issues/pull_requests/users: gh repos gh search_repos gh starred gh issues gh pull_requests gh search_issues gh notifications gh trending gh user gh me Usage: gh view [index] [-b/--browser] Example(s): gh repos gh view 1 gh starred gh view 1 -b gh view 1 --browser :type github: :class:`github.GitHub` :param github: An instance of `github.GitHub`. :type index: str :param index: Determines the index to view. :type browser: bool :param browser: Determines whether to view the profile in a browser, or in the terminal. """ github.view(int(index), browser) gitsome-0.8.0/gitsome/lib/000077500000000000000000000000001345243314000154025ustar00rootroot00000000000000gitsome-0.8.0/gitsome/lib/__init__.py000066400000000000000000000011021345243314000175050ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2015 Donne Martin. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. gitsome-0.8.0/gitsome/lib/debug_timer.py000066400000000000000000000021221345243314000202370ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2015 Donne Martin. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://www.apache.org/licenses/LICENSE-2.0 # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from __future__ import print_function from __future__ import division import click import time def timeit(method): """From: https://www.andreas-jung.com/contents/a-python-decorator-for-measuring-the-execution-time-of-methods # NOQA """ def timed(*args, **kw): ts = time.time() result = method(*args, **kw) te = time.time() message = '%r (%r, %r) %2.2f sec' % (method.__name__, args, kw, te-ts) click.secho(message + '\n', fg='red') return result return timed gitsome-0.8.0/gitsome/lib/github3/000077500000000000000000000000001345243314000167475ustar00rootroot00000000000000gitsome-0.8.0/gitsome/lib/github3/__about__.py000066400000000000000000000011241345243314000212250ustar00rootroot00000000000000"""The module that holds much of the metadata about github3.py.""" __package_name__ = 'github3.py' __title__ = 'github3' __author__ = 'Ian Cordasco' __author_email__ = 'graffatcolmingov@gmail.com' __license__ = 'Modified BSD' __copyright__ = 'Copyright 2012-2016 Ian Cordasco' __version__ = '1.0.0a4' __version_info__ = tuple(int(i) for i in __version__.split('.') if i.isdigit()) __url__ = 'http://github3.readthedocs.org' __all__ = ( '__package_name__', '__title__', '__author__', '__author_email__', '__license__', '__copyright__', '__version__', '__version_info__', '__url__', ) gitsome-0.8.0/gitsome/lib/github3/__init__.py000066400000000000000000000037571345243314000210740ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ github3 ======= See http://github3.readthedocs.org/ for documentation. :copyright: (c) 2012-2016 by Ian Cordasco :license: Modified BSD, see LICENSE for more details """ from .__about__ import ( __package_name__, __title__, __author__, __author_email__, __license__, __copyright__, __version__, __version_info__, __url__, ) from .api import ( authorize, login, enterprise_login, emojis, gist, gitignore_template, create_gist, issue, markdown, octocat, organization, pull_request, followers_of, followed_by, public_gists, gists_by, issues_on, gitignore_templates, all_repositories, all_users, all_events, organizations_with, repositories_by, starred_by, subscriptions_for, rate_limit, repository, search_code, search_repositories, search_users, search_issues, user, zen ) from .github import GitHub, GitHubEnterprise, GitHubStatus from .exceptions import ( BadRequest, AuthenticationFailed, ForbiddenError, GitHubError, MethodNotAllowed, NotFoundError, ServerError, NotAcceptable, UnprocessableEntity ) __all__ = ( 'AuthenticationFailed', 'BadRequest', 'ForbiddenError', 'GitHub', 'GitHubEnterprise', 'GitHubError', 'GitHubStatus', 'MethodNotAllowed', 'NotAcceptable', 'NotFoundError', 'ServerError', 'UnprocessableEntity', 'authorize', 'login', 'enterprise_login', 'emojis', 'gist', 'gitignore_template', 'create_gist', 'issue', 'markdown', 'octocat', 'organization', 'pull_request', 'followers_of', 'followed_by', 'public_gists', 'gists_by', 'issues_on', 'gitignore_templates', 'all_repositories', 'all_users', 'all_events', 'organizations_with', 'repositories_by', 'starred_by', 'subscriptions_for', 'rate_limit', 'repository', 'search_code', 'search_repositories', 'search_users', 'search_issues', 'user', 'zen', # Metadata attributes '__package_name__', '__title__', '__author__', '__author_email__', '__license__', '__copyright__', '__version__', '__version_info__', '__url__', ) gitsome-0.8.0/gitsome/lib/github3/api.py000066400000000000000000000566261345243314000201110ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ github3.api =========== :copyright: (c) 2012-2014 by Ian Cordasco :license: Modified BSD, see LICENSE for more details """ from .github import GitHub, GitHubEnterprise gh = GitHub() def authorize(username, password, scopes, note='', note_url='', client_id='', client_secret='', two_factor_callback=None): """Obtain an authorization token for the GitHub API. :param str username: (required) :param str password: (required) :param list scopes: (required), areas you want this token to apply to, i.e., 'gist', 'user' :param str note: (optional), note about the authorization :param str note_url: (optional), url for the application :param str client_id: (optional), 20 character OAuth client key for which to create a token :param str client_secret: (optional), 40 character OAuth client secret for which to create the token :param func two_factor_callback: (optional), function to call when a Two-Factor Authentication code needs to be provided by the user. :returns: :class:`Authorization ` """ gh = GitHub() gh.login(two_factor_callback=two_factor_callback) return gh.authorize(username, password, scopes, note, note_url, client_id, client_secret) def login(username=None, password=None, token=None, two_factor_callback=None): """Construct and return an authenticated GitHub session. .. note:: To allow you to specify either a username and password combination or a token, none of the parameters are required. If you provide none of them, you will receive a :class:`NullObject ` :param str username: login name :param str password: password for the login :param str token: OAuth token :param func two_factor_callback: (optional), function you implement to provide the Two Factor Authentication code to GitHub when necessary :returns: :class:`GitHub ` """ g = None if (username and password) or token: g = GitHub() g.login(username, password, token, two_factor_callback) return g def enterprise_login(username=None, password=None, token=None, url=None, two_factor_callback=None, verify=True): """Construct and return an authenticated GitHubEnterprise session. .. note:: To allow you to specify either a username and password combination or a token, none of the parameters are required. If you provide none of them, you will receive a :class:`NullObject ` :param str username: login name :param str password: password for the login :param str token: OAuth token :param str url: URL of a GitHub Enterprise instance :param func two_factor_callback: (optional), function you implement to provide the Two Factor Authentication code to GitHub when necessary :returns: :class:`GitHubEnterprise ` """ if not url: raise ValueError('GitHub Enterprise requires you provide the URL of' ' the instance') g = None if (username and password) or token: g = GitHubEnterprise(url, verify=verify) g.login(username, password, token, two_factor_callback) return g def emojis(): return gh.emojis() emojis.__doc__ = gh.emojis.__doc__ def gist(id_num): """Retrieve the gist identified by ``id_num``. :param int id_num: (required), unique id of the gist :returns: :class:`Gist ` """ return gh.gist(id_num) def gitignore_template(language): """Return the template for language. :returns: str """ return gh.gitignore_template(language) def gitignore_templates(): """Return the list of available templates. :returns: list of template names """ return gh.gitignore_templates() def all_repositories(number=-1, etag=None): """Iterate over every repository in the order they were created. :param int number: (optional), number of repositories to return. Default: -1, returns all of them :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Repository ` """ return gh.all_repositories(number, etag) def all_users(number=-1, etag=None): """Iterate over every user in the order they signed up for GitHub. :param int number: (optional), number of users to return. Default: -1, returns all of them :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`User ` """ return gh.all_users(number, etag) def all_events(number=-1, etag=None): """Iterate over public events. :param int number: (optional), number of events to return. Default: -1 returns all available events :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Event ` """ return gh.all_events(number, etag) def followers_of(username, number=-1, etag=None): """List the followers of ``username``. :param str username: (required), login of the person to list the followers of :param int number: (optional), number of followers to return, Default: -1, return all of them :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`User ` """ return gh.followers_of(username, number, etag) if username else [] def followed_by(username, number=-1, etag=None): """List the people ``username`` follows. :param str username: (required), login of the user :param int number: (optional), number of users being followed by username to return. Default: -1, return all of them :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`User ` """ return gh.followed_by(username, number, etag) if username else [] def public_gists(number=-1, etag=None): """Iterate over all public gists. .. versionadded:: 1.0 This was split from ``github3.iter_gists`` before 1.0. :param int number: (optional), number of gists to return. Default: -1, return all of them :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Gist ` """ return gh.public_gists(number, etag) def gists_by(username, number=-1, etag=None): """Iterate over gists created by the provided username. :param str username: (required), if provided, get the gists for this user instead of the authenticated user. :param int number: (optional), number of gists to return. Default: -1, return all of them :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Gist ` """ if username: return gh.gists_by(username, number, etag) return iter([]) def issues_on(owner, repository, milestone=None, state=None, assignee=None, mentioned=None, labels=None, sort=None, direction=None, since=None, number=-1, etag=None): """Iterate over issues on owner/repository. .. versionchanged:: 0.9.0 - The ``state`` parameter now accepts 'all' in addition to 'open' and 'closed'. :param str owner: login of the owner of the repository :param str repository: name of the repository :param int milestone: None, '*', or ID of milestone :param str state: accepted values: ('all', 'open', 'closed') api-default: 'open' :param str assignee: '*' or login of the user :param str mentioned: login of the user :param str labels: comma-separated list of label names, e.g., 'bug,ui,@high' :param str sort: accepted values: ('created', 'updated', 'comments') api-default: created :param str direction: accepted values: ('asc', 'desc') api-default: desc :param since: (optional), Only issues after this date will be returned. This can be a `datetime` or an ISO8601 formatted date string, e.g., 2012-05-20T23:10:27Z :type since: datetime or string :param int number: (optional), number of issues to return. Default: -1 returns all issues :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Issue `\ s """ if owner and repository: return gh.issues_on(owner, repository, milestone, state, assignee, mentioned, labels, sort, direction, since, number, etag) return iter([]) def organizations_with(username, number=-1, etag=None): """List the organizations with ``username`` as a member. :param str username: (required), login of the user :param int number: (optional), number of orgs to return. Default: -1, return all of the issues :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Organization ` """ return gh.organizations_with(username, number, etag) def repositories_by(username, type=None, sort=None, direction=None, number=-1, etag=None): """List public repositories for the specified ``username``. .. versionadded:: 0.6 .. note:: This replaces github3.iter_repos :param str username: (required) :param str type: (optional), accepted values: ('all', 'owner', 'member') API default: 'all' :param str sort: (optional), accepted values: ('created', 'updated', 'pushed', 'full_name') API default: 'created' :param str direction: (optional), accepted values: ('asc', 'desc'), API default: 'asc' when using 'full_name', 'desc' otherwise :param int number: (optional), number of repositories to return. Default: -1 returns all repositories :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Repository ` objects """ if login: return gh.repositories_by(username, type, sort, direction, number, etag) return iter([]) def starred_by(username, number=-1, etag=None): """Iterate over repositories starred by ``username``. :param str username: (optional), name of user whose stars you want to see :param int number: (optional), number of repositories to return. Default: -1 returns all repositories :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Repository ` """ return gh.starred_by(username, number, etag) def subscriptions_for(username, number=-1, etag=None): """Iterate over repositories subscribed to by ``username``. :param str username: name of user whose subscriptions you want to see :param int number: (optional), number of repositories to return. Default: -1 returns all repositories :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Repository ` """ return gh.subscriptions_for(username, number, etag) def create_gist(description, files): """Create an anonymous public gist. :param str description: (required), short description of the gist :param dict files: (required), file names with associated dictionaries for content, e.g. {'spam.txt': {'content': 'File contents ...'}} :returns: :class:`Gist ` """ return gh.create_gist(description, files) # (No coverage) def issue(owner, repository, number): """Anonymously gets issue :number on :owner/:repository. :param str owner: (required), repository owner :param str repository: (required), repository name :param int number: (required), issue number :returns: :class:`Issue ` """ return gh.issue(owner, repository, number) def markdown(text, mode='', context='', raw=False): """Render an arbitrary markdown document. :param str text: (required), the text of the document to render :param str mode: (optional), 'markdown' or 'gfm' :param str context: (optional), only important when using mode 'gfm', this is the repository to use as the context for the rendering :param bool raw: (optional), renders a document like a README.md, no gfm, no context :returns: str -- HTML formatted text """ return gh.markdown(text, mode, context, raw) def octocat(say=None): """Return an easter egg from the API. :params str say: (optional), pass in what you'd like Octocat to say :returns: ascii art of Octocat """ return gh.octocat(say) def organization(name): return gh.organization(name) organization.__doc__ = gh.organization.__doc__ def pull_request(owner, repository, number): """Anonymously retrieve pull request :number on :owner/:repository. :param str owner: (required), repository owner :param str repository: (required), repository name :param int number: (required), pull request number :returns: :class:`PullRequest ` """ return gh.pull_request(owner, repository, number) def rate_limit(): return gh.rate_limit() rate_limit.__doc__ = gh.rate_limit.__doc__ def repository(owner, repository): return gh.repository(owner, repository) repository.__doc__ = gh.repository.__doc__ def search_code(query, sort=None, order=None, per_page=None, text_match=False, number=-1, etag=None): """Find code via the code search API. .. warning:: You will only be able to make 5 calls with this or other search functions. To raise the rate-limit on this set of endpoints, create an authenticated :class:`GitHub ` Session with ``login``. The query can contain any combination of the following supported qualifiers: - ``in`` Qualifies which fields are searched. With this qualifier you can restrict the search to just the file contents, the file path, or both. - ``language`` Searches code based on the language it’s written in. - ``fork`` Specifies that code from forked repositories should be searched. Repository forks will not be searchable unless the fork has more stars than the parent repository. - ``size`` Finds files that match a certain size (in bytes). - ``path`` Specifies the path that the resulting file must be at. - ``extension`` Matches files with a certain extension. - ``user`` or ``repo`` Limits searches to a specific user or repository. For more information about these qualifiers, see: http://git.io/-DvAuA :param str query: (required), a valid query as described above, e.g., ``addClass in:file language:js repo:jquery/jquery`` :param str sort: (optional), how the results should be sorted; option(s): ``indexed``; default: best match :param str order: (optional), the direction of the sorted results, options: ``asc``, ``desc``; default: ``desc`` :param int per_page: (optional) :param bool text_match: (optional), if True, return matching search terms. See http://git.io/4ct1eQ for more information :param int number: (optional), number of repositories to return. Default: -1, returns all available repositories :param str etag: (optional), previous ETag header value :return: generator of :class:`CodeSearchResult ` """ return gh.search_code(query, sort, order, per_page, text_match, number, etag) def search_issues(query, sort=None, order=None, per_page=None, text_match=False, number=-1, etag=None): """Find issues by state and keyword .. warning:: You will only be able to make 5 calls with this or other search functions. To raise the rate-limit on this set of endpoints, create an authenticated :class:`GitHub ` Session with ``login``. The query can contain any combination of the following supported qualifers: - ``type`` With this qualifier you can restrict the search to issues or pull request only. - ``in`` Qualifies which fields are searched. With this qualifier you can restrict the search to just the title, body, comments, or any combination of these. - ``author`` Finds issues created by a certain user. - ``assignee`` Finds issues that are assigned to a certain user. - ``mentions`` Finds issues that mention a certain user. - ``commenter`` Finds issues that a certain user commented on. - ``involves`` Finds issues that were either created by a certain user, assigned to that user, mention that user, or were commented on by that user. - ``state`` Filter issues based on whether they’re open or closed. - ``labels`` Filters issues based on their labels. - ``language`` Searches for issues within repositories that match a certain language. - ``created`` or ``updated`` Filters issues based on times of creation, or when they were last updated. - ``comments`` Filters issues based on the quantity of comments. - ``user`` or ``repo`` Limits searches to a specific user or repository. For more information about these qualifiers, see: http://git.io/d1oELA :param str query: (required), a valid query as described above, e.g., ``windows label:bug`` :param str sort: (optional), how the results should be sorted; options: ``created``, ``comments``, ``updated``; default: best match :param str order: (optional), the direction of the sorted results, options: ``asc``, ``desc``; default: ``desc`` :param int per_page: (optional) :param bool text_match: (optional), if True, return matching search terms. See http://git.io/QLQuSQ for more information :param int number: (optional), number of issues to return. Default: -1, returns all available issues :param str etag: (optional), previous ETag header value :return: generator of :class:`IssueSearchResult ` """ return gh.search_issues(query, sort, order, per_page, text_match, number, etag) def search_repositories(query, sort=None, order=None, per_page=None, text_match=False, number=-1, etag=None): """Find repositories via various criteria. .. warning:: You will only be able to make 5 calls with this or other search functions. To raise the rate-limit on this set of endpoints, create an authenticated :class:`GitHub ` Session with ``login``. The query can contain any combination of the following supported qualifers: - ``in`` Qualifies which fields are searched. With this qualifier you can restrict the search to just the repository name, description, readme, or any combination of these. - ``size`` Finds repositories that match a certain size (in kilobytes). - ``forks`` Filters repositories based on the number of forks, and/or whether forked repositories should be included in the results at all. - ``created`` or ``pushed`` Filters repositories based on times of creation, or when they were last updated. Format: ``YYYY-MM-DD``. Examples: ``created:<2011``, ``pushed:<2013-02``, ``pushed:>=2013-03-06`` - ``user`` or ``repo`` Limits searches to a specific user or repository. - ``language`` Searches repositories based on the language they're written in. - ``stars`` Searches repositories based on the number of stars. For more information about these qualifiers, see: http://git.io/4Z8AkA :param str query: (required), a valid query as described above, e.g., ``tetris language:assembly`` :param str sort: (optional), how the results should be sorted; options: ``stars``, ``forks``, ``updated``; default: best match :param str order: (optional), the direction of the sorted results, options: ``asc``, ``desc``; default: ``desc`` :param int per_page: (optional) :param bool text_match: (optional), if True, return matching search terms. See http://git.io/4ct1eQ for more information :param int number: (optional), number of repositories to return. Default: -1, returns all available repositories :param str etag: (optional), previous ETag header value :return: generator of :class:`Repository ` """ return gh.search_repositories(query, sort, order, per_page, text_match, number, etag) def search_users(query, sort=None, order=None, per_page=None, text_match=False, number=-1, etag=None): """Find users via the Search API. .. warning:: You will only be able to make 5 calls with this or other search functions. To raise the rate-limit on this set of endpoints, create an authenticated :class:`GitHub ` Session with ``login``. The query can contain any combination of the following supported qualifers: - ``type`` With this qualifier you can restrict the search to just personal accounts or just organization accounts. - ``in`` Qualifies which fields are searched. With this qualifier you can restrict the search to just the username, public email, full name, or any combination of these. - ``repos`` Filters users based on the number of repositories they have. - ``location`` Filter users by the location indicated in their profile. - ``language`` Search for users that have repositories that match a certain language. - ``created`` Filter users based on when they joined. - ``followers`` Filter users based on the number of followers they have. For more information about these qualifiers see: http://git.io/wjVYJw :param str query: (required), a valid query as described above, e.g., ``tom repos:>42 followers:>1000`` :param str sort: (optional), how the results should be sorted; options: ``followers``, ``repositories``, or ``joined``; default: best match :param str order: (optional), the direction of the sorted results, options: ``asc``, ``desc``; default: ``desc`` :param int per_page: (optional) :param bool text_match: (optional), if True, return matching search terms. See http://git.io/_V1zRwa for more information :param int number: (optional), number of search results to return; Default: -1 returns all available :param str etag: (optional), ETag header value of the last request. :return: generator of :class:`UserSearchResult ` """ return gh.search_users(query, sort, order, per_page, text_match, number, etag) def user(username): return gh.user(username) user.__doc__ = gh.user.__doc__ def zen(): """Return a quote from the Zen of GitHub. Yet another API Easter Egg. :returns: str """ return gh.zen() gitsome-0.8.0/gitsome/lib/github3/auths.py000066400000000000000000000075641345243314000204610ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ github3.auths ============= This module contains the Authorization object. """ from __future__ import unicode_literals from .decorators import requires_basic_auth from .models import GitHubCore class Authorization(GitHubCore): """The :class:`Authorization ` object. Two authorization instances can be checked like so:: a1 == a2 a1 != a2 And is equivalent to:: a1.id == a2.id a1.id != a2.id See also: http://developer.github.com/v3/oauth/#oauth-authorizations-api """ def _update_attributes(self, auth): self._api = auth.get('url') #: Details about the application (name, url) self.app = auth.get('app', {}) #: Returns the Authorization token self.token = auth.get('token', '') #: App name self.name = self.app.get('name', '') #: URL about the note self.note_url = auth.get('note_url') or '' #: Note about the authorization self.note = auth.get('note') or '' #: List of scopes this applies to self.scopes = auth.get('scopes', []) #: Unique id of the authorization self.id = auth.get('id', 0) #: datetime object representing when the authorization was created. self.created_at = self._strptime(auth.get('created_at')) #: datetime object representing when the authorization was updated. self.updated_at = self._strptime(auth.get('updated_at')) def _repr(self): return ''.format(self.name) def _update(self, scopes_data, note, note_url): """Helper for add_scopes, replace_scopes, remove_scopes.""" if note is not None: scopes_data['note'] = note if note_url is not None: scopes_data['note_url'] = note_url json = self._json(self._post(self._api, data=scopes_data), 200) if json: self._update_attributes(json) return True return False @requires_basic_auth def add_scopes(self, scopes, note=None, note_url=None): """Adds the scopes to this authorization. .. versionadded:: 1.0 :param list scopes: Adds these scopes to the ones present on this authorization :param str note: (optional), Note about the authorization :param str note_url: (optional), URL to link to when the user views the authorization :returns: True if successful, False otherwise :rtype: bool """ return self._update({'add_scopes': scopes}, note, note_url) @requires_basic_auth def delete(self): """Delete this authorization.""" return self._boolean(self._delete(self._api), 204, 404) @requires_basic_auth def remove_scopes(self, scopes, note=None, note_url=None): """Remove the scopes from this authorization. .. versionadded:: 1.0 :param list scopes: Remove these scopes from the ones present on this authorization :param str note: (optional), Note about the authorization :param str note_url: (optional), URL to link to when the user views the authorization :returns: True if successful, False otherwise :rtype: bool """ return self._update({'rm_scopes': scopes}, note, note_url) @requires_basic_auth def replace_scopes(self, scopes, note=None, note_url=None): """Replace the scopes on this authorization. .. versionadded:: 1.0 :param list scopes: Use these scopes instead of the previous list :param str note: (optional), Note about the authorization :param str note_url: (optional), URL to link to when the user views the authorization :returns: True if successful, False otherwise :rtype: bool """ return self._update({'scopes': scopes}, note, note_url) gitsome-0.8.0/gitsome/lib/github3/decorators.py000066400000000000000000000057231345243314000214750ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ github3.decorators ================== This module provides decorators to the rest of the library """ from functools import wraps from requests.models import Response import os try: # (No coverage) # python2 from StringIO import StringIO # (No coverage) except ImportError: # (No coverage) # python3 from io import BytesIO as StringIO class RequestsStringIO(StringIO): def read(self, n=-1, *args, **kwargs): # StringIO is an old-style class, so can't use super return StringIO.read(self, n) def requires_auth(func): """Decorator to note which object methods require authorization.""" @wraps(func) def auth_wrapper(self, *args, **kwargs): if hasattr(self, 'session') and self.session.has_auth(): return func(self, *args, **kwargs) else: from .exceptions import error_for # Mock a 401 response r = generate_fake_error_response( '{"message": "Requires authentication"}' ) raise error_for(r) return auth_wrapper def requires_basic_auth(func): """Specific (basic) authentication decorator. This is used to note which object methods require username/password authorization and won't work with token based authorization. """ @wraps(func) def auth_wrapper(self, *args, **kwargs): if hasattr(self, 'session') and self.session.auth: return func(self, *args, **kwargs) else: from .exceptions import error_for # Mock a 401 response r = generate_fake_error_response( '{"message": "Requires username/password authentication"}' ) raise error_for(r) return auth_wrapper def requires_app_credentials(func): """Require client_id and client_secret to be associated. This is used to note and enforce which methods require a client_id and client_secret to be used. """ @wraps(func) def auth_wrapper(self, *args, **kwargs): client_id, client_secret = self.session.retrieve_client_credentials() if client_id and client_secret: return func(self, *args, **kwargs) else: from .exceptions import error_for # Mock a 401 response r = generate_fake_error_response( '{"message": "Requires username/password authentication"}' ) raise error_for(r) return auth_wrapper def generate_fake_error_response(msg, status_code=401, encoding='utf-8'): r = Response() r.status_code = status_code r.encoding = encoding r.raw = RequestsStringIO(msg.encode()) r._content_consumed = True r._content = r.raw.read() return r # Use mock decorators when generating documentation, so all functino signatures # are displayed correctly if os.getenv('GENERATING_DOCUMENTATION', None) == 'github3': requires_auth = requires_basic_auth = lambda x: x # noqa # (No coverage) gitsome-0.8.0/gitsome/lib/github3/events.py000066400000000000000000000123771345243314000206370ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ github3.events ============== This module contains the class(es) related to Events """ from __future__ import unicode_literals from .models import GitHubCore class Event(GitHubCore): """The :class:`Event ` object. It structures and handles the data returned by via the `Events `_ section of the GitHub API. Two events can be compared like so:: e1 == e2 e1 != e2 And that is equivalent to:: e1.id == e2.id e1.id != e2.id """ def _update_attributes(self, event): from .users import User from .orgs import Organization #: :class:`User ` object representing the actor. self.actor = User(event.get('actor')) if event.get('actor') else None #: datetime object representing when the event was created. self.created_at = self._strptime(event.get('created_at')) #: Unique id of the event self.id = event.get('id') #: List all possible types of Events self.org = None if event.get('org'): self.org = Organization(event.get('org')) #: Event type http://developer.github.com/v3/activity/events/types/ self.type = event.get('type') handler = _payload_handlers.get(self.type, identity) #: Dictionary with the payload. Payload structure is defined by type_. # _type: http://developer.github.com/v3/events/types self.payload = handler(event.get('payload'), self) #: Return ``tuple(owner, repository_name)`` self.repo = event.get('repo') if self.repo is not None: self.repo = tuple(self.repo['name'].split('/')) #: Indicates whether the Event is public or not. self.public = event.get('public') def _repr(self): return ''.format(self.type[:-5]) @staticmethod def list_types(): """List available payload types.""" return sorted(_payload_handlers.keys()) def _commitcomment(payload, session): from .repos.comment import RepoComment if payload.get('comment'): payload['comment'] = RepoComment(payload['comment'], session) return payload def _follow(payload, session): from .users import User if payload.get('target'): payload['target'] = User(payload['target'], session) return payload def _forkev(payload, session): from .repos import Repository if payload.get('forkee'): payload['forkee'] = Repository(payload['forkee'], session) return payload def _gist(payload, session): from .gists import Gist if payload.get('gist'): payload['gist'] = Gist(payload['gist'], session) return payload def _issuecomm(payload, session): from .issues import Issue from .issues.comment import IssueComment if payload.get('issue'): payload['issue'] = Issue(payload['issue'], session) if payload.get('comment'): payload['comment'] = IssueComment(payload['comment'], session) return payload def _issueevent(payload, session): from .issues import Issue if payload.get('issue'): payload['issue'] = Issue(payload['issue'], session) return payload def _member(payload, session): from .users import User if payload.get('member'): payload['member'] = User(payload['member'], session) return payload def _pullreqev(payload, session): from .pulls import PullRequest if payload.get('pull_request'): payload['pull_request'] = PullRequest(payload['pull_request'], session) return payload def _pullreqcomm(payload, session): from .pulls import PullRequest, ReviewComment # Transform the Pull Request attribute pull = payload.get('pull_request') if pull: payload['pull_request'] = PullRequest(pull, session) # Transform the Comment attribute comment = payload.get('comment') if comment: payload['comment'] = ReviewComment(comment, session) return payload def _release(payload, session): from .repos.release import Release release = payload.get('release') if release: payload['release'] = Release(release, session) return payload def _team(payload, session): from .orgs import Team from .repos import Repository from .users import User if payload.get('team'): payload['team'] = Team(payload['team'], session) if payload.get('repo'): payload['repo'] = Repository(payload['repo'], session) if payload.get('sender'): payload['sender'] = User(payload['sender'], session) return payload def identity(x, session): return x _payload_handlers = { 'CommitCommentEvent': _commitcomment, 'CreateEvent': identity, 'DeleteEvent': identity, 'FollowEvent': _follow, 'ForkEvent': _forkev, 'ForkApplyEvent': identity, 'GistEvent': _gist, 'GollumEvent': identity, 'IssueCommentEvent': _issuecomm, 'IssuesEvent': _issueevent, 'MemberEvent': _member, 'PublicEvent': identity, 'PullRequestEvent': _pullreqev, 'PullRequestReviewCommentEvent': _pullreqcomm, 'PushEvent': identity, 'ReleaseEvent': _release, 'StatusEvent': identity, 'TeamAddEvent': _team, 'WatchEvent': identity, } gitsome-0.8.0/gitsome/lib/github3/exceptions.py000066400000000000000000000060671345243314000215130ustar00rootroot00000000000000# -*- coding: utf-8 -*- """All exceptions for the github3 library.""" class GitHubError(Exception): """The base exception class.""" def __init__(self, resp): super(GitHubError, self).__init__(resp) #: Response code that triggered the error self.response = resp self.code = resp.status_code self.errors = [] try: error = resp.json() #: Message associated with the error self.msg = error.get('message') #: List of errors provided by GitHub if error.get('errors'): self.errors = error.get('errors') except: # Amazon S3 error self.msg = resp.content or '[No message]' def __repr__(self): return '<{0} [{1}]>'.format(self.__class__.__name__, self.msg or self.code) def __str__(self): return '{0} {1}'.format(self.code, self.msg) @property def message(self): """The actual message returned by the API.""" return self.msg class UnprocessableResponseBody(GitHubError): """Exception class for response objects that cannot be handled.""" def __init__(self, message, body): Exception.__init__(self, message) self.body = body self.msg = message def __repr__(self): return '<{0} [{1}]>'.format('UnprocessableResponseBody', self.body) def __str__(self): return self.message class BadRequest(GitHubError): """Exception class for 400 responses.""" pass class AuthenticationFailed(GitHubError): """Exception class for 401 responses. Possible reasons: - Need one time password (for two-factor authentication) - You are not authorized to access the resource """ pass class ForbiddenError(GitHubError): """Exception class for 403 responses. Possible reasons: - Too many requests (you've exceeded the ratelimit) - Too many login failures """ pass class NotFoundError(GitHubError): """Exception class for 404 responses.""" pass class MethodNotAllowed(GitHubError): """Exception class for 405 responses.""" pass class NotAcceptable(GitHubError): """Exception class for 406 responses.""" pass class UnprocessableEntity(GitHubError): """Exception class for 422 responses.""" pass class ClientError(GitHubError): """Catch-all for 400 responses that aren't specific errors.""" pass class ServerError(GitHubError): """Exception class for 5xx responses.""" pass error_classes = { 400: BadRequest, 401: AuthenticationFailed, 403: ForbiddenError, 404: NotFoundError, 405: MethodNotAllowed, 406: NotAcceptable, 422: UnprocessableEntity, } def error_for(response): """Return the appropriate initialized exception class for a response.""" klass = error_classes.get(response.status_code) if klass is None: if 400 <= response.status_code < 500: klass = ClientError if 500 <= response.status_code < 600: klass = ServerError return klass(response) gitsome-0.8.0/gitsome/lib/github3/gists/000077500000000000000000000000001345243314000201005ustar00rootroot00000000000000gitsome-0.8.0/gitsome/lib/github3/gists/__init__.py000066400000000000000000000004451345243314000222140ustar00rootroot00000000000000""" github3.gists ============= Module which contains all the gist related material. Sub-modules: github3.gists.gist github3.gists.file github3.gists.comment github3.gists.history See also: http://developer.github.com/v3/gists/ """ from .gist import Gist __all__ = [Gist] gitsome-0.8.0/gitsome/lib/github3/gists/comment.py000066400000000000000000000017011345243314000221130ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ github3.gists.comment --------------------- Module containing the logic for a GistComment """ from __future__ import unicode_literals from ..models import BaseComment from ..users import User class GistComment(BaseComment): """This object represents a comment on a gist. Two comment instances can be checked like so:: c1 == c2 c1 != c2 And is equivalent to:: c1.id == c2.id c1.id != c2.id See also: http://developer.github.com/v3/gists/comments/ """ def _update_attributes(self, comment): self._api = comment.get('url') #: :class:`User ` who made the comment #: Unless it is not associated with an account self.user = None if comment.get('user'): self.user = User(comment.get('user'), self) # (No coverage) def _repr(self): return ''.format(self.user.login) gitsome-0.8.0/gitsome/lib/github3/gists/file.py000066400000000000000000000024621345243314000213750ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ github3.gists.file ------------------ Module containing the logic for the GistFile object. """ from __future__ import unicode_literals from ..models import GitHubCore class GistFile(GitHubCore): """This represents the file object returned by interacting with gists. It stores the raw url of the file, the file name, language, size and content. """ def _update_attributes(self, attributes): #: The raw URL for the file at GitHub. self.raw_url = attributes.get('raw_url') #: The name of the file. self.filename = attributes.get('filename') #: The name of the file. self.name = attributes.get('filename') #: The language associated with the file. self.language = attributes.get('language') #: The size of the file. self.size = attributes.get('size') #: The content of the file. self.original_content = attributes.get('content') def _repr(self): return ''.format(self.name) def content(self): """Retrieve contents of file from key 'raw_url' if there is no 'content' key in Gist object. """ resp = self._get(self.raw_url) if self._boolean(resp, 200, 404): return resp.content return None gitsome-0.8.0/gitsome/lib/github3/gists/gist.py000066400000000000000000000176131345243314000214300ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ github3.gists.gist ================== This module contains the Gist class alone for simplicity. """ from __future__ import unicode_literals from json import dumps from ..models import GitHubCore from ..decorators import requires_auth from .comment import GistComment from .file import GistFile from .history import GistHistory from ..users import User class Gist(GitHubCore): """This object holds all the information returned by Github about a gist. With it you can comment on or fork the gist (assuming you are authenticated), edit or delete the gist (assuming you own it). You can also "star" or "unstar" the gist (again assuming you have authenticated). Two gist instances can be checked like so:: g1 == g2 g1 != g2 And is equivalent to:: g1.id == g2.id g1.id != g2.id See also: http://developer.github.com/v3/gists/ """ def _update_attributes(self, data): #: Number of comments on this gist self.comments_count = data.get('comments', 0) #: Unique id for this gist. self.id = '{0}'.format(data.get('id', '')) #: Description of the gist self.description = data.get('description', '') # e.g. https://api.github.com/gists/1 self._api = data.get('url', '') #: URL of this gist at Github, e.g., https://gist.github.com/1 self.html_url = data.get('html_url') #: Boolean describing if the gist is public or private self.public = data.get('public') self._forks = data.get('forks', []) #: Git URL to pull this gist, e.g., git://gist.github.com/1.git self.git_pull_url = data.get('git_pull_url', '') #: Git URL to push to gist, e.g., git@gist.github.com/1.git self.git_push_url = data.get('git_push_url', '') #: datetime object representing when the gist was created. self.created_at = self._strptime(data.get('created_at')) #: datetime object representing the last time this gist was updated. self.updated_at = self._strptime(data.get('updated_at')) owner = data.get('owner') #: :class:`User ` object representing the owner of #: the gist. self.owner = User(owner, self) if owner else None self._files = [GistFile(data['files'][f]) for f in data['files']] #: History of this gist, list of #: :class:`GistHistory ` self.history = [GistHistory(h, self) for h in data.get('history', [])] # New urls #: Comments URL (not a template) self.comments_url = data.get('comments_url', '') #: Commits URL (not a template) self.commits_url = data.get('commits_url', '') #: Forks URL (not a template) self.forks_url = data.get('forks_url', '') #: Whether the content of this Gist has been truncated or not self.truncated = data.get('truncated') def __str__(self): return self.id def _repr(self): return ''.format(self.id) @requires_auth def create_comment(self, body): """Create a comment on this gist. :param str body: (required), body of the comment :returns: :class:`GistComment ` """ json = None if body: url = self._build_url('comments', base_url=self._api) json = self._json(self._post(url, data={'body': body}), 201) return self._instance_or_null(GistComment, json) @requires_auth def delete(self): """Delete this gist. :returns: bool -- whether the deletion was successful """ return self._boolean(self._delete(self._api), 204, 404) @requires_auth def edit(self, description='', files={}): """Edit this gist. :param str description: (optional), description of the gist :param dict files: (optional), files that make up this gist; the key(s) should be the file name(s) and the values should be another (optional) dictionary with (optional) keys: 'content' and 'filename' where the former is the content of the file and the latter is the new name of the file. :returns: bool -- whether the edit was successful """ data = {} json = None if description: data['description'] = description if files: data['files'] = files if data: json = self._json(self._patch(self._api, data=dumps(data)), 200) if json: self._update_attributes(json) return True return False @requires_auth def fork(self): """Fork this gist. :returns: :class:`Gist ` if successful, ``None`` otherwise """ url = self._build_url('forks', base_url=self._api) json = self._json(self._post(url), 201) return self._instance_or_null(Gist, json) @requires_auth def is_starred(self): """Check to see if this gist is starred by the authenticated user. :returns: bool -- True if it is starred, False otherwise """ url = self._build_url('star', base_url=self._api) return self._boolean(self._get(url), 204, 404) def comments(self, number=-1, etag=None): """Iterate over comments on this gist. :param int number: (optional), number of comments to iterate over. Default: -1 will iterate over all comments on the gist :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`GistComment ` """ url = self._build_url('comments', base_url=self._api) return self._iter(int(number), url, GistComment, etag=etag) def commits(self, number=-1, etag=None): """Iterate over the commits on this gist. These commits will be requested from the API and should be the same as what is in ``Gist.history``. .. versionadded:: 0.6 .. versionchanged:: 0.9 Added param ``etag``. :param int number: (optional), number of commits to iterate over. Default: -1 will iterate over all commits associated with this gist. :param str etag: (optional), ETag from a previous request to this endpoint. :returns: generator of :class:`GistHistory ` """ url = self._build_url('commits', base_url=self._api) return self._iter(int(number), url, GistHistory) def files(self): """Iterator over the files stored in this gist. :returns: generator of :class`GistFile ` """ return iter(self._files) def forks(self, number=-1, etag=None): """Iterator of forks of this gist. .. versionchanged:: 0.9 Added params ``number`` and ``etag``. :param int number: (optional), number of forks to iterate over. Default: -1 will iterate over all forks of this gist. :param str etag: (optional), ETag from a previous request to this endpoint. :returns: generator of :class:`Gist ` """ url = self._build_url('forks', base_url=self._api) return self._iter(int(number), url, Gist, etag=etag) @requires_auth def star(self): """Star this gist. :returns: bool -- True if successful, False otherwise """ url = self._build_url('star', base_url=self._api) return self._boolean(self._put(url), 204, 404) @requires_auth def unstar(self): """Un-star this gist. :returns: bool -- True if successful, False otherwise """ url = self._build_url('star', base_url=self._api) return self._boolean(self._delete(url), 204, 404) gitsome-0.8.0/gitsome/lib/github3/gists/history.py000066400000000000000000000033501345243314000221540ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ github3.gists.history --------------------- Module containing the logic for the GistHistory object. """ from __future__ import unicode_literals from ..models import GitHubCore from ..users import User class GistHistory(GitHubCore): """Thisobject represents one version (or revision) of a gist. Two history instances can be checked like so:: h1 == h2 h1 != h2 And is equivalent to:: h1.version == h2.version h1.version != h2.version """ def _update_attributes(self, history): self._api = history.get('url', '') #: SHA of the commit associated with this version self.version = history.get('version', '') #: user who made these changes self.user = User(history.get('user') or {}, self) #: dict containing the change status; see also: deletions, additions, #: total self.change_status = history.get('change_status', {}) #: number of additions made self.additions = self.change_status.get('additions', 0) #: number of deletions made self.deletions = self.change_status.get('deletions', 0) #: total number of changes made self.total = self.change_status.get('total', 0) #: datetime representation of when the commit was made self.committed_at = self._strptime(history.get('committed_at')) def _repr(self): return ''.format(self.version) def get_gist(self): """Retrieve the gist at this version. :returns: :class:`Gist ` """ from .gist import Gist json = self._json(self._get(self._api), 200) return self._instance_or_null(Gist, json) gitsome-0.8.0/gitsome/lib/github3/git.py000066400000000000000000000151711345243314000201110ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ github3.git =========== This module contains all the classes relating to Git Data. See also: http://developer.github.com/v3/git/ """ from __future__ import unicode_literals from json import dumps from base64 import b64decode from .models import GitHubCore, BaseCommit from .users import User from .decorators import requires_auth class Blob(GitHubCore): """The :class:`Blob ` object. See also: http://developer.github.com/v3/git/blobs/ """ def _update_attributes(self, blob): self._api = blob.get('url', '') #: Raw content of the blob. self.content = blob.get('content').encode() #: Encoding of the raw content. self.encoding = blob.get('encoding') #: Decoded content of the blob. self.decoded = self.content if self.encoding == 'base64': self.decoded = b64decode(self.content) #: Size of the blob in bytes self.size = blob.get('size') #: SHA1 of the blob self.sha = blob.get('sha') def _repr(self): return ''.format(self.sha) class GitData(GitHubCore): """The :class:`GitData ` object. This isn't directly returned to the user (developer) ever. This is used to prevent duplication of some common items among other Git Data objects. """ def _update_attributes(self, data): #: SHA of the object self.sha = data.get('sha') self._api = data.get('url', '') class Commit(BaseCommit): """The :class:`Commit ` object. This represents a commit made in a repository. See also: http://developer.github.com/v3/git/commits/ """ def _update_attributes(self, commit): super(Commit, self)._update_attributes(commit) #: dict containing at least the name, email and date the commit was #: created self.author = commit.get('author', {}) or {} # If GH returns nil/None then make sure author is a dict self._author_name = self.author.get('name', '') #: dict containing similar information to the author attribute self.committer = commit.get('committer', {}) or {} # blank the data if GH returns no data self._commit_name = self.committer.get('name', '') #: :class:`Tree ` the commit belongs to. self.tree = None if commit.get('tree'): self.tree = Tree(commit.get('tree'), self) def _repr(self): return ''.format(self._author_name, self.sha) def author_as_User(self): """Attempt to return the author attribute as a :class:`User `. No guarantees are made about the validity of this object, i.e., having a login or created_at object. """ return User(self.author, self) def committer_as_User(self): """Attempt to return the committer attribute as a :class:`User ` object. No guarantees are made about the validity of this object. """ return User(self.committer, self) class Reference(GitHubCore): """The :class:`Reference ` object. This represents a reference created on a repository. See also: http://developer.github.com/v3/git/refs/ """ def _update_attributes(self, ref): self._api = ref.get('url', '') #: The reference path, e.g., refs/heads/sc/featureA self.ref = ref.get('ref') #: :class:`GitObject ` the reference points to self.object = GitObject(ref.get('object', {})) def _repr(self): return ''.format(self.ref) @requires_auth def delete(self): """Delete this reference. :returns: bool """ return self._boolean(self._delete(self._api), 204, 404) @requires_auth def update(self, sha, force=False): """Update this reference. :param str sha: (required), sha of the reference :param bool force: (optional), force the update or not :returns: bool """ data = {'sha': sha, 'force': force} json = self._json(self._patch(self._api, data=dumps(data)), 200) if json: self._update_attributes(json) return True return False class GitObject(GitData): """The :class:`GitObject ` object.""" def _update_attributes(self, obj): super(GitObject, self)._update_attributes(obj) #: The type of object. self.type = obj.get('type') def _repr(self): return ''.format(self.sha) class Tag(GitData): """The :class:`Tag ` object. See also: http://developer.github.com/v3/git/tags/ """ def _update_attributes(self, tag): super(Tag, self)._update_attributes(tag) #: String of the tag self.tag = tag.get('tag') #: Commit message for the tag self.message = tag.get('message') #: dict containing the name and email of the person self.tagger = tag.get('tagger') #: :class:`GitObject ` for the tag self.object = GitObject(tag.get('object', {})) def _repr(self): return ''.format(self.tag) class Tree(GitData): """The :class:`Tree ` object. See also: http://developer.github.com/v3/git/trees/ """ def _update_attributes(self, tree): super(Tree, self)._update_attributes(tree) #: list of :class:`Hash ` objects self.tree = [Hash(t) for t in tree.get('tree', [])] def _repr(self): return ''.format(self.sha) def __eq__(self, other): return self.as_dict() == other.as_dict() def __ne__(self, other): return self.as_dict() != other.as_dict() def recurse(self): """Recurse into the tree. :returns: :class:`Tree ` """ json = self._json(self._get(self._api, params={'recursive': '1'}), 200) return self._instance_or_null(Tree, json) class Hash(GitHubCore): """The :class:`Hash ` object. See also: http://developer.github.com/v3/git/trees/#create-a-tree """ def _update_attributes(self, info): #: Path to file self.path = info.get('path') #: File mode self.mode = info.get('mode') #: Type of hash, e.g., blob self.type = info.get('type') #: Size of hash self.size = info.get('size') #: SHA of the hash self.sha = info.get('sha') #: URL of this object in the GitHub API self.url = info.get('url') def _repr(self): return ''.format(self.sha) gitsome-0.8.0/gitsome/lib/github3/github.py000066400000000000000000002147611345243314000206160ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ github3.github ============== This module contains the main GitHub session object. """ from __future__ import unicode_literals import json from .auths import Authorization from .decorators import (requires_auth, requires_basic_auth, requires_app_credentials) from .events import Event from .gists import Gist from .issues import Issue, issue_params from .models import GitHubCore from .orgs import Membership, Organization, Team from .pulls import PullRequest from .repos.repo import Repository, repo_issue_params from .search import (CodeSearchResult, IssueSearchResult, RepositorySearchResult, UserSearchResult) from .structs import SearchIterator from . import users from .notifications import Thread from .licenses import License from uritemplate import URITemplate class GitHub(GitHubCore): """Stores all the session information. There are two ways to log into the GitHub API :: from github3 import login g = login(user, password) g = login(token=token) g = login(user, token=token) or :: from github3 import GitHub g = GitHub(user, password) g = GitHub(token=token) g = GitHub(user, token=token) This is simple backward compatibility since originally there was no way to call the GitHub object with authentication parameters. """ def __init__(self, username='', password='', token=''): super(GitHub, self).__init__({}) if token: self.login(username, token=token) elif username and password: self.login(username, password) def _repr(self): if self.session.auth: return ''.format(self.session.auth) return ''.format(id(self)) @requires_auth def add_email_addresses(self, addresses=[]): """Add the email addresses in ``addresses`` to the authenticated user's account. :param list addresses: (optional), email addresses to be added :returns: list of :class:`~github3.users.Email` """ json = [] if addresses: url = self._build_url('user', 'emails') json = self._json(self._post(url, data=addresses), 201) return [users.Email(email) for email in json] if json else [] def all_events(self, number=-1, etag=None): """Iterate over public events. :param int number: (optional), number of events to return. Default: -1 returns all available events :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Event `\ s """ url = self._build_url('events') return self._iter(int(number), url, Event, etag=etag) def all_organizations(self, number=-1, since=None, etag=None, per_page=None): """Iterate over every organization in the order they were created. :param int number: (optional), number of organizations to return. Default: -1, returns all of them :param int since: (optional), last organization id seen (allows restarting this iteration) :param str etag: (optional), ETag from a previous request to the same endpoint :param int per_page: (optional), number of organizations to list per request :returns: generator of :class:`Organization ` """ url = self._build_url('organizations') return self._iter(int(number), url, Organization, params={'since': since, 'per_page': per_page}, etag=etag) def all_repositories(self, number=-1, since=None, etag=None, per_page=None): """Iterate over every repository in the order they were created. :param int number: (optional), number of repositories to return. Default: -1, returns all of them :param int since: (optional), last repository id seen (allows restarting this iteration) :param str etag: (optional), ETag from a previous request to the same endpoint :param int per_page: (optional), number of repositories to list per request :returns: generator of :class:`Repository ` """ url = self._build_url('repositories') return self._iter(int(number), url, Repository, params={'since': since, 'per_page': per_page}, etag=etag) def all_users(self, number=-1, etag=None, per_page=None, since=None): """Iterate over every user in the order they signed up for GitHub. .. versionchanged:: 1.0.0 Inserted the ``since`` parameter after the ``number`` parameter. :param int number: (optional), number of users to return. Default: -1, returns all of them :param int since: (optional), ID of the last user that you've seen. :param str etag: (optional), ETag from a previous request to the same endpoint :param int per_page: (optional), number of users to list per request :returns: generator of :class:`User ` """ url = self._build_url('users') return self._iter(int(number), url, users.User, etag=etag, params={'per_page': per_page, 'since': since}) @requires_basic_auth def authorization(self, id_num): """Get information about authorization ``id``. :param int id_num: (required), unique id of the authorization :returns: :class:`Authorization ` """ json = None if int(id_num) > 0: url = self._build_url('authorizations', str(id_num)) json = self._json(self._get(url), 200) return self._instance_or_null(Authorization, json) @requires_basic_auth def authorizations(self, number=-1, etag=None): """Iterate over authorizations for the authenticated user. This will return a 404 if you are using a token for authentication. :param int number: (optional), number of authorizations to return. Default: -1 returns all available authorizations :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Authorization `\ s """ url = self._build_url('authorizations') return self._iter(int(number), url, Authorization, etag=etag) def authorize(self, username, password, scopes=None, note='', note_url='', client_id='', client_secret=''): """Obtain an authorization token. The retrieved token will allow future consumers to use the API without a username and password. :param str username: (required) :param str password: (required) :param list scopes: (optional), areas you want this token to apply to, i.e., 'gist', 'user' :param str note: (optional), note about the authorization :param str note_url: (optional), url for the application :param str client_id: (optional), 20 character OAuth client key for which to create a token :param str client_secret: (optional), 40 character OAuth client secret for which to create the token :returns: :class:`Authorization ` """ json = None if username and password: url = self._build_url('authorizations') data = {'note': note, 'note_url': note_url, 'client_id': client_id, 'client_secret': client_secret} if scopes: data['scopes'] = scopes with self.session.temporary_basic_auth(username, password): json = self._json(self._post(url, data=data), 201) return self._instance_or_null(Authorization, json) def check_authorization(self, access_token): """Check an authorization created by a registered application. OAuth applications can use this method to check token validity without hitting normal rate limits because of failed login attempts. If the token is valid, it will return True, otherwise it will return False. :returns: bool """ p = self.session.params auth = (p.get('client_id'), p.get('client_secret')) if access_token and auth: url = self._build_url('applications', str(auth[0]), 'tokens', str(access_token)) resp = self._get(url, auth=auth, params={ 'client_id': None, 'client_secret': None }) return self._boolean(resp, 200, 404) return False def create_gist(self, description, files, public=True): """Create a new gist. If no login was provided, it will be anonymous. :param str description: (required), description of gist :param dict files: (required), file names with associated dictionaries for content, e.g. ``{'spam.txt': {'content': 'File contents ...'}}`` :param bool public: (optional), make the gist public if True :returns: :class:`Gist ` """ new_gist = {'description': description, 'public': public, 'files': files} url = self._build_url('gists') json = self._json(self._post(url, data=new_gist), 201) return self._instance_or_null(Gist, json) @requires_auth def create_issue(self, owner, repository, title, body=None, assignee=None, milestone=None, labels=[]): """Create an issue on the project 'repository' owned by 'owner' with title 'title'. ``body``, ``assignee``, ``milestone``, ``labels`` are all optional. .. warning:: This method retrieves the repository first and then uses it to create an issue. If you're making several issues, you should use :py:meth:`repository ` and then use :py:meth:`create_issue ` :param str owner: (required), login of the owner :param str repository: (required), repository name :param str title: (required), Title of issue to be created :param str body: (optional), The text of the issue, markdown formatted :param str assignee: (optional), Login of person to assign the issue to :param int milestone: (optional), id number of the milestone to attribute this issue to (e.g. ``m`` is a :class:`Milestone ` object, ``m.number`` is what you pass here.) :param list labels: (optional), List of label names. :returns: :class:`Issue ` if successful """ repo = None if owner and repository and title: repo = self.repository(owner, repository) # repo can be None or a NullObject. # If repo is None, than one of owner, repository, or title were # False-y. If repo is a NullObject then owner/repository 404's. if repo is not None: # If repo is a NullObject then that's most likely because the # repository was not found (404). In that case, calling the # create_issue method will still return # which will ideally help the user understand what went wrong. return repo.create_issue(title, body, assignee, milestone, labels) return self._instance_or_null(Issue, None) @requires_auth def create_key(self, title, key, read_only=False): """Create a new key for the authenticated user. :param str title: (required), key title :param str key: (required), actual key contents, accepts path as a string or file-like object :param bool read_only: (optional), restrict key access to read-only, default to False :returns: :class:`Key ` """ json = None if title and key: data = {'title': title, 'key': key, 'read_only': read_only} url = self._build_url('user', 'keys') req = self._post(url, data=data) json = self._json(req, 201) return self._instance_or_null(users.Key, json) @requires_auth def create_repository(self, name, description='', homepage='', private=False, has_issues=True, has_wiki=True, auto_init=False, gitignore_template=''): """Create a repository for the authenticated user. :param str name: (required), name of the repository :param str description: (optional) :param str homepage: (optional) :param str private: (optional), If ``True``, create a private repository. API default: ``False`` :param bool has_issues: (optional), If ``True``, enable issues for this repository. API default: ``True`` :param bool has_wiki: (optional), If ``True``, enable the wiki for this repository. API default: ``True`` :param bool auto_init: (optional), auto initialize the repository :param str gitignore_template: (optional), name of the git template to use; ignored if auto_init = False. :returns: :class:`Repository ` .. warning: ``name`` should be no longer than 100 characters """ url = self._build_url('user', 'repos') data = {'name': name, 'description': description, 'homepage': homepage, 'private': private, 'has_issues': has_issues, 'has_wiki': has_wiki, 'auto_init': auto_init, 'gitignore_template': gitignore_template} json = self._json(self._post(url, data=data), 201) return self._instance_or_null(Repository, json) @requires_auth def delete_email_addresses(self, addresses=[]): """Delete the email addresses in ``addresses`` from the authenticated user's account. :param list addresses: (optional), email addresses to be removed :returns: bool """ url = self._build_url('user', 'emails') return self._boolean(self._delete(url, data=json.dumps(addresses)), 204, 404) @requires_auth def emails(self, number=-1, etag=None): """Iterate over email addresses for the authenticated user. :param int number: (optional), number of email addresses to return. Default: -1 returns all available email addresses :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of dicts """ url = self._build_url('user', 'emails') return self._iter(int(number), url, users.Email, etag=etag) def emojis(self): """Retrieves a dictionary of all of the emojis that GitHub supports. :returns: dictionary where the key is what would be in between the colons and the value is the URL to the image, e.g., :: { '+1': 'https://github.global.ssl.fastly.net/images/...', # ... } """ url = self._build_url('emojis') return self._json(self._get(url), 200, include_cache_info=False) @requires_basic_auth def feeds(self): """List GitHub's timeline resources in Atom format. :returns: dictionary parsed to include URITemplates """ def replace_href(feed_dict): if not feed_dict: return feed_dict ret_dict = {} # Let's pluck out what we're most interested in, the href value href = feed_dict.pop('href', None) # Then we update the return dictionary with the rest of the values ret_dict.update(feed_dict) if href is not None: # So long as there is something to template, let's template it ret_dict['href'] = URITemplate(href) return ret_dict url = self._build_url('feeds') json = self._json(self._get(url), 200, include_cache_info=False) if json is None: # If something went wrong, get out early return None # We have a response body to parse feeds = {} # Let's pop out the old links so we don't have to skip them below old_links = json.pop('_links', {}) _links = {} # If _links is in the response JSON, iterate over that and recreate it # so that any templates contained inside can be turned into # URITemplates for key, value in old_links.items(): if isinstance(value, list): # If it's an array/list of links, let's replace that with a # new list of links _links[key] = [replace_href(d) for d in value] else: # Otherwise, just use the new value _links[key] = replace_href(value) # Start building up our return dictionary feeds['_links'] = _links for key, value in json.items(): # This should roughly be the same logic as above. if isinstance(value, list): feeds[key] = [URITemplate(v) for v in value] else: feeds[key] = URITemplate(value) return feeds @requires_auth def follow(self, username): """Make the authenticated user follow the provided username. :param str username: (required), user to follow :returns: bool """ resp = False if username: url = self._build_url('user', 'following', username) resp = self._boolean(self._put(url), 204, 404) return resp def followed_by(self, username, number=-1, etag=None): """Iterate over users being followed by ``username``. .. versionadded:: 1.0.0 This replaces iter_following('sigmavirus24'). :param str username: (required), login of the user to check :param int number: (optional), number of people to return. Default: -1 returns all people you follow :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`User `\ s """ url = self._build_url('users', username, 'following') return self._iter(int(number), url, users.User, etag=etag) @requires_auth def followers(self, number=-1, etag=None): """Iterate over followers of the authenticated user. .. versionadded:: 1.0.0 This replaces iter_followers(). :param int number: (optional), number of followers to return. Default: -1 returns all followers :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`User `\ s """ url = self._build_url('user', 'followers') return self._iter(int(number), url, users.User, etag=etag) def followers_of(self, username, number=-1, etag=None): """Iterate over followers of ``username``. .. versionadded:: 1.0.0 This replaces iter_followers('sigmavirus24'). :param str username: (required), login of the user to check :param int number: (optional), number of followers to return. Default: -1 returns all followers :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`User `\ s """ url = self._build_url('users', username, 'followers') return self._iter(int(number), url, users.User, etag=etag) @requires_auth def following(self, number=-1, etag=None): """Iterate over users the authenticated user is following. .. versionadded:: 1.0.0 This replaces iter_following(). :param int number: (optional), number of people to return. Default: -1 returns all people you follow :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`User `\ s """ url = self._build_url('user', 'following') return self._iter(int(number), url, users.User, etag=etag) def gist(self, id_num): """Retrieve the gist using the specified id number. :param int id_num: (required), unique id of the gist :returns: :class:`Gist ` """ url = self._build_url('gists', str(id_num)) json = self._json(self._get(url), 200) return self._instance_or_null(Gist, json) @requires_auth def gists(self, number=-1, etag=None): """Retrieve the authenticated user's gists. .. versionadded:: 1.0 :param int number: (optional), number of gists to return. Default: -1, returns all available gists :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Gist `\ s """ url = self._build_url('gists') return self._iter(int(number), url, Gist, etag=etag) def gists_by(self, username, number=-1, etag=None): """Iterate over the gists owned by a user. .. versionadded:: 1.0 :param str username: login of the user who owns the gists :param int number: (optional), number of gists to return. Default: -1 returns all available gists :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Gist `\ s """ url = self._build_url('users', username, 'gists') return self._iter(int(number), url, Gist, etag=etag) def gitignore_template(self, language): """Return the template for language. :returns: str """ url = self._build_url('gitignore', 'templates', language) json = self._json(self._get(url), 200) if not json: return '' return json.get('source', '') def gitignore_templates(self): """Return the list of available templates. :returns: list of template names """ url = self._build_url('gitignore', 'templates') return self._json(self._get(url), 200) or [] @requires_auth def is_following(self, username): """Check if the authenticated user is following login. :param str username: (required), login of the user to check if the authenticated user is checking :returns: bool """ json = False if username: url = self._build_url('user', 'following', username) json = self._boolean(self._get(url), 204, 404) return json @requires_auth def is_starred(self, username, repo): """Check if the authenticated user starred username/repo. :param str username: (required), owner of repository :param str repo: (required), name of repository :returns: bool """ json = False if username and repo: url = self._build_url('user', 'starred', username, repo) json = self._boolean(self._get(url), 204, 404) return json def issue(self, username, repository, number): """Fetch issue from owner/repository. :param str username: (required), owner of the repository :param str repository: (required), name of the repository :param int number: (required), issue number :return: :class:`Issue ` """ json = None if username and repository and int(number) > 0: url = self._build_url('repos', username, repository, 'issues', str(number)) json = self._json(self._get(url), 200) return self._instance_or_null(Issue, json) @requires_auth def issues(self, filter='', state='', labels='', sort='', direction='', since=None, number=-1, etag=None): """List all of the authenticated user's (and organization's) issues. .. versionchanged:: 0.9.0 - The ``state`` parameter now accepts 'all' in addition to 'open' and 'closed'. :param str filter: accepted values: ('assigned', 'created', 'mentioned', 'subscribed') api-default: 'assigned' :param str state: accepted values: ('all', 'open', 'closed') api-default: 'open' :param str labels: comma-separated list of label names, e.g., 'bug,ui,@high' :param str sort: accepted values: ('created', 'updated', 'comments') api-default: created :param str direction: accepted values: ('asc', 'desc') api-default: desc :param since: (optional), Only issues after this date will be returned. This can be a `datetime` or an ISO8601 formatted date string, e.g., 2012-05-20T23:10:27Z :type since: datetime or string :param int number: (optional), number of issues to return. Default: -1 returns all issues :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Issue ` """ url = self._build_url('issues') # issue_params will handle the since parameter params = issue_params(filter, state, labels, sort, direction, since) return self._iter(int(number), url, Issue, params, etag) def issues_on(self, username, repository, milestone=None, state=None, assignee=None, mentioned=None, labels=None, sort=None, direction=None, since=None, number=-1, etag=None): """List issues on owner/repository. Only owner and repository are required. .. versionchanged:: 0.9.0 - The ``state`` parameter now accepts 'all' in addition to 'open' and 'closed'. :param str username: login of the owner of the repository :param str repository: name of the repository :param int milestone: None, '*', or ID of milestone :param str state: accepted values: ('all', 'open', 'closed') api-default: 'open' :param str assignee: '*' or login of the user :param str mentioned: login of the user :param str labels: comma-separated list of label names, e.g., 'bug,ui,@high' :param str sort: accepted values: ('created', 'updated', 'comments') api-default: created :param str direction: accepted values: ('asc', 'desc') api-default: desc :param since: (optional), Only issues after this date will be returned. This can be a `datetime` or an ISO8601 formatted date string, e.g., 2012-05-20T23:10:27Z :type since: datetime or string :param int number: (optional), number of issues to return. Default: -1 returns all issues :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Issue `\ s """ if username and repository: url = self._build_url('repos', username, repository, 'issues') params = repo_issue_params(milestone, state, assignee, mentioned, labels, sort, direction, since) return self._iter(int(number), url, Issue, params=params, etag=etag) return iter([]) @requires_auth def key(self, id_num): """Gets the authenticated user's key specified by id_num. :param int id_num: (required), unique id of the key :returns: :class:`Key ` """ json = None if int(id_num) > 0: url = self._build_url('user', 'keys', str(id_num)) json = self._json(self._get(url), 200) return self._instance_or_null(users.Key, json) @requires_auth def keys(self, number=-1, etag=None): """Iterate over public keys for the authenticated user. :param int number: (optional), number of keys to return. Default: -1 returns all your keys :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Key `\ s """ url = self._build_url('user', 'keys') return self._iter(int(number), url, users.Key, etag=etag) def license(self, name): """Retrieve the license specified by the name. :param string name: (required), name of license :returns: :class:`License ` """ url = self._build_url('licenses', name) json = self._json(self._get(url, headers=License.CUSTOM_HEADERS), 200) return self._instance_or_null(License, json) def licenses(self, number=-1, etag=None): """Iterate over open source licenses. :returns: generator of :class:`License ` """ url = self._build_url('licenses') return self._iter(int(number), url, License, etag=etag, headers=License.CUSTOM_HEADERS) def login(self, username=None, password=None, token=None, two_factor_callback=None): """Logs the user into GitHub for protected API calls. :param str username: login name :param str password: password for the login :param str token: OAuth token :param func two_factor_callback: (optional), function you implement to provide the Two Factor Authentication code to GitHub when necessary """ if username and password: self.session.basic_auth(username, password) elif token: self.session.token_auth(token) # The Session method handles None for free. self.session.two_factor_auth_callback(two_factor_callback) def markdown(self, text, mode='', context='', raw=False): """Render an arbitrary markdown document. :param str text: (required), the text of the document to render :param str mode: (optional), 'markdown' or 'gfm' :param str context: (optional), only important when using mode 'gfm', this is the repository to use as the context for the rendering :param bool raw: (optional), renders a document like a README.md, no gfm, no context :returns: str (or unicode on Python 2) -- HTML formatted text """ data = None json = False headers = {} if raw: url = self._build_url('markdown', 'raw') data = text headers['content-type'] = 'text/plain' else: url = self._build_url('markdown') data = {} if text: data['text'] = text if mode in ('markdown', 'gfm'): data['mode'] = mode if context: data['context'] = context json = True html = '' if data: req = self._post(url, data=data, json=json, headers=headers) if req.ok: html = req.text return html @requires_auth def me(self): """Retrieves the info for the authenticated user. .. versionadded:: 1.0 This was separated from the ``user`` method. :returns: The representation of the authenticated user. :rtype: :class:`User ` """ url = self._build_url('user') json = self._json(self._get(url), 200) return self._instance_or_null(users.User, json) @requires_auth def membership_in(self, organization): """Retrieve the user's membership in the specified organization.""" url = self._build_url('user', 'memberships', 'orgs', str(organization)) json = self._json(self._get(url), 200) return self._instance_or_null(Membership, json) def meta(self): """Returns a dictionary with arrays of addresses in CIDR format specifying theaddresses that the incoming service hooks will originate from. .. versionadded:: 0.5 """ url = self._build_url('meta') return self._json(self._get(url), 200) or {} @requires_auth def notifications(self, all=False, participating=False, number=-1, etag=None): """Iterate over the user's notification. :param bool all: (optional), iterate over all notifications :param bool participating: (optional), only iterate over notifications in which the user is participating :param int number: (optional), how many notifications to return :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Thread ` """ params = None if all is True: params = {'all': 'true'} elif participating is True: params = {'participating': 'true'} url = self._build_url('notifications') return self._iter(int(number), url, Thread, params, etag=etag) def octocat(self, say=None): """Returns an easter egg of the API. :params str say: (optional), pass in what you'd like Octocat to say :returns: ascii art of Octocat :rtype: str (or unicode on Python 3) """ url = self._build_url('octocat') req = self._get(url, params={'s': say}) return req.text if req.ok else '' def organization(self, username): """Returns a Organization object for the login name :param str username: (required), login name of the org :returns: :class:`Organization ` """ url = self._build_url('orgs', username) json = self._json(self._get(url), 200) return self._instance_or_null(Organization, json) @requires_auth def organization_issues(self, name, filter='', state='', labels='', sort='', direction='', since=None, number=-1, etag=None): """Iterate over the organization's issues if the authenticated user belongs to it. :param str name: (required), name of the organization :param str filter: accepted values: ('assigned', 'created', 'mentioned', 'subscribed') api-default: 'assigned' :param str state: accepted values: ('open', 'closed') api-default: 'open' :param str labels: comma-separated list of label names, e.g., 'bug,ui,@high' :param str sort: accepted values: ('created', 'updated', 'comments') api-default: created :param str direction: accepted values: ('asc', 'desc') api-default: desc :param since: (optional), Only issues after this date will be returned. This can be a `datetime` or an ISO8601 formatted date string, e.g., 2012-05-20T23:10:27Z :type since: datetime or string :param int number: (optional), number of issues to return. Default: -1, returns all available issues :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Issue ` """ url = self._build_url('orgs', name, 'issues') # issue_params will handle the since parameter params = issue_params(filter, state, labels, sort, direction, since) return self._iter(int(number), url, Issue, params, etag) @requires_auth def organizations(self, number=-1, etag=None): """Iterate over all organizations the authenticated user belongs to. This will display both the private memberships and the publicized memberships. :param int number: (optional), number of organizations to return. Default: -1 returns all available organizations :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Organization `\ s """ url = self._build_url('user', 'orgs') return self._iter(int(number), url, Organization, etag=etag) def organizations_with(self, username, number=-1, etag=None): """Iterate over organizations with ``username`` as a public member. .. versionadded:: 1.0.0 Replaces ``iter_orgs('sigmavirus24')``. :param str username: (optional), user whose orgs you wish to list :param int number: (optional), number of organizations to return. Default: -1 returns all available organizations :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Organization `\ s """ if username: url = self._build_url('users', username, 'orgs') return self._iter(int(number), url, Organization, etag=etag) return iter([]) def public_gists(self, number=-1, etag=None): """Retrieve all public gists and iterate over them. .. versionadded:: 1.0 :param int number: (optional), number of gists to return. Default: -1 returns all available gists :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Gist `\ s """ url = self._build_url('gists', 'public') return self._iter(int(number), url, Gist, etag=etag) @requires_auth def organization_memberships(self, state=None, number=-1, etag=None): """List organizations of which the user is a current or pending member. :param str state: (option), state of the membership, i.e., active, pending :returns: iterator of :class:`Membership ` """ params = None url = self._build_url('user', 'memberships', 'orgs') if state is not None and state.lower() in ('active', 'pending'): params = {'state': state.lower()} return self._iter(int(number), url, Membership, params=params, etag=etag) @requires_auth def pubsubhubbub(self, mode, topic, callback, secret=''): """Create/update a pubsubhubbub hook. :param str mode: (required), accepted values: ('subscribe', 'unsubscribe') :param str topic: (required), form: https://github.com/:user/:repo/events/:event :param str callback: (required), the URI that receives the updates :param str secret: (optional), shared secret key that generates a SHA1 HMAC of the payload content. :returns: bool """ from re import match m = match('https?://[\w\d\-\.\:]+/\w+/[\w\._-]+/events/\w+', topic) status = False if mode and topic and callback and m: data = [('hub.mode', mode), ('hub.topic', topic), ('hub.callback', callback)] if secret: data.append(('hub.secret', secret)) url = self._build_url('hub') # This is not JSON data. It is meant to be form data # application/x-www-form-urlencoded works fine here, no need for # multipart/form-data status = self._boolean(self._post(url, data=data, json=False), 204, 404) return status def pull_request(self, owner, repository, number): """Fetch pull_request #:number: from :owner:/:repository :param str owner: (required), owner of the repository :param str repository: (required), name of the repository :param int number: (required), issue number :return: :class:`~github.pulls.PullRequest` """ json = None if int(number) > 0: url = self._build_url('repos', owner, repository, 'pulls', str(number)) json = self._json(self._get(url), 200) return self._instance_or_null(PullRequest, json) def rate_limit(self): """Returns a dictionary with information from /rate_limit. The dictionary has two keys: ``resources`` and ``rate``. In ``resources`` you can access information about ``core`` or ``search``. Note: the ``rate`` key will be deprecated before version 3 of the GitHub API is finalized. Do not rely on that key. Instead, make your code future-proof by using ``core`` in ``resources``, e.g., :: rates = g.rate_limit() rates['resources']['core'] # => your normal ratelimit info rates['resources']['search'] # => your search ratelimit info .. versionadded:: 0.8 :returns: dict """ url = self._build_url('rate_limit') return self._json(self._get(url), 200) @requires_auth def repositories(self, username='', type=None, sort=None, direction=None, number=-1, etag=None): """List repositories for the given user, filterable by ``type``. .. versionchanged:: 0.6 Removed the login parameter for correctness. Use repositories_by instead :param str username: (optional), username :param str type: (optional), accepted values: ('all', 'owner', 'public', 'private', 'member') API default: 'all' :param str sort: (optional), accepted values: ('created', 'updated', 'pushed', 'full_name') API default: 'created' :param str direction: (optional), accepted values: ('asc', 'desc'), API default: 'asc' when using 'full_name', 'desc' otherwise :param int number: (optional), number of repositories to return. Default: -1 returns all repositories :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Repository ` objects """ if username: url = self._build_url('users', username, 'repos') else: url = self._build_url('user', 'repos') params = {} if type in ('all', 'owner', 'public', 'private', 'member'): params.update(type=type) if sort in ('created', 'updated', 'pushed', 'full_name'): params.update(sort=sort) if direction in ('asc', 'desc'): params.update(direction=direction) return self._iter(int(number), url, Repository, params, etag) def repositories_by(self, username, type=None, sort=None, direction=None, number=-1, etag=None): """List public repositories for the specified ``username``. .. versionadded:: 0.6 :param str username: (required), username :param str type: (optional), accepted values: ('all', 'owner', 'member') API default: 'all' :param str sort: (optional), accepted values: ('created', 'updated', 'pushed', 'full_name') API default: 'created' :param str direction: (optional), accepted values: ('asc', 'desc'), API default: 'asc' when using 'full_name', 'desc' otherwise :param int number: (optional), number of repositories to return. Default: -1 returns all repositories :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Repository ` objects """ url = self._build_url('users', username, 'repos') params = {} if type in ('all', 'owner', 'member'): params.update(type=type) if sort in ('created', 'updated', 'pushed', 'full_name'): params.update(sort=sort) if direction in ('asc', 'desc'): params.update(direction=direction) return self._iter(int(number), url, Repository, params, etag) def repository(self, owner, repository): """Returns a Repository object for the specified combination of owner and repository :param str owner: (required) :param str repository: (required) :returns: :class:`Repository ` """ json = None if owner and repository: url = self._build_url('repos', owner, repository) json = self._json(self._get(url), 200) return self._instance_or_null(Repository, json) def repository_with_id(self, number): """Returns the Repository with id ``number``. :param int number: id of the repository :returns: :class:`Repository ` """ number = int(number) json = None if number > 0: url = self._build_url('repositories', str(number)) json = self._json(self._get(url), 200) return self._instance_or_null(Repository, json) @requires_app_credentials def revoke_authorization(self, access_token): """Revoke specified authorization for an OAuth application. Revoke all authorization tokens created by your application. This will only work if you have already called ``set_client_id``. :param str access_token: (required), the access_token to revoke :returns: bool -- True if successful, False otherwise """ client_id, client_secret = self.session.retrieve_client_credentials() url = self._build_url('applications', str(client_id), 'tokens', access_token) with self.session.temporary_basic_auth(client_id, client_secret): response = self._delete(url, params={'client_id': None, 'client_secret': None}) return self._boolean(response, 204, 404) @requires_app_credentials def revoke_authorizations(self): """Revoke all authorizations for an OAuth application. Revoke all authorization tokens created by your application. This will only work if you have already called ``set_client_id``. :param str client_id: (required), the client_id of your application :returns: bool -- True if successful, False otherwise """ client_id, client_secret = self.session.retrieve_client_credentials() url = self._build_url('applications', str(client_id), 'tokens') with self.session.temporary_basic_auth(client_id, client_secret): response = self._delete(url, params={'client_id': None, 'client_secret': None}) return self._boolean(response, 204, 404) def search_code(self, query, sort=None, order=None, per_page=None, text_match=False, number=-1, etag=None): """Find code via the code search API. The query can contain any combination of the following supported qualifiers: - ``in`` Qualifies which fields are searched. With this qualifier you can restrict the search to just the file contents, the file path, or both. - ``language`` Searches code based on the language it’s written in. - ``fork`` Specifies that code from forked repositories should be searched. Repository forks will not be searchable unless the fork has more stars than the parent repository. - ``size`` Finds files that match a certain size (in bytes). - ``path`` Specifies the path that the resulting file must be at. - ``extension`` Matches files with a certain extension. - ``user`` or ``repo`` Limits searches to a specific user or repository. For more information about these qualifiers, see: http://git.io/-DvAuA :param str query: (required), a valid query as described above, e.g., ``addClass in:file language:js repo:jquery/jquery`` :param str sort: (optional), how the results should be sorted; option(s): ``indexed``; default: best match :param str order: (optional), the direction of the sorted results, options: ``asc``, ``desc``; default: ``desc`` :param int per_page: (optional) :param bool text_match: (optional), if True, return matching search terms. See http://git.io/iRmJxg for more information :param int number: (optional), number of repositories to return. Default: -1, returns all available repositories :param str etag: (optional), previous ETag header value :return: generator of :class:`CodeSearchResult ` """ params = {'q': query} headers = {} if sort == 'indexed': params['sort'] = sort if sort and order in ('asc', 'desc'): params['order'] = order if text_match: headers = { 'Accept': 'application/vnd.github.v3.full.text-match+json' } url = self._build_url('search', 'code') return SearchIterator(number, url, CodeSearchResult, self, params, etag, headers) def search_issues(self, query, sort=None, order=None, per_page=None, text_match=False, number=-1, etag=None): """Find issues by state and keyword The query can contain any combination of the following supported qualifers: - ``type`` With this qualifier you can restrict the search to issues or pull request only. - ``in`` Qualifies which fields are searched. With this qualifier you can restrict the search to just the title, body, comments, or any combination of these. - ``author`` Finds issues created by a certain user. - ``assignee`` Finds issues that are assigned to a certain user. - ``mentions`` Finds issues that mention a certain user. - ``commenter`` Finds issues that a certain user commented on. - ``involves`` Finds issues that were either created by a certain user, assigned to that user, mention that user, or were commented on by that user. - ``state`` Filter issues based on whether they’re open or closed. - ``labels`` Filters issues based on their labels. - ``language`` Searches for issues within repositories that match a certain language. - ``created`` or ``updated`` Filters issues based on times of creation, or when they were last updated. - ``comments`` Filters issues based on the quantity of comments. - ``user`` or ``repo`` Limits searches to a specific user or repository. For more information about these qualifiers, see: http://git.io/d1oELA :param str query: (required), a valid query as described above, e.g., ``windows label:bug`` :param str sort: (optional), how the results should be sorted; options: ``created``, ``comments``, ``updated``; default: best match :param str order: (optional), the direction of the sorted results, options: ``asc``, ``desc``; default: ``desc`` :param int per_page: (optional) :param bool text_match: (optional), if True, return matching search terms. See http://git.io/QLQuSQ for more information :param int number: (optional), number of issues to return. Default: -1, returns all available issues :param str etag: (optional), previous ETag header value :return: generator of :class:`IssueSearchResult ` """ params = {'q': query} headers = {} if sort in ('comments', 'created', 'updated'): params['sort'] = sort if order in ('asc', 'desc'): params['order'] = order if text_match: headers = { 'Accept': 'application/vnd.github.v3.full.text-match+json' } url = self._build_url('search', 'issues') return SearchIterator(number, url, IssueSearchResult, self, params, etag, headers) def search_repositories(self, query, sort=None, order=None, per_page=None, text_match=False, number=-1, etag=None): """Find repositories via various criteria. The query can contain any combination of the following supported qualifers: - ``in`` Qualifies which fields are searched. With this qualifier you can restrict the search to just the repository name, description, readme, or any combination of these. - ``size`` Finds repositories that match a certain size (in kilobytes). - ``forks`` Filters repositories based on the number of forks, and/or whether forked repositories should be included in the results at all. - ``created`` or ``pushed`` Filters repositories based on times of creation, or when they were last updated. Format: ``YYYY-MM-DD``. Examples: ``created:<2011``, ``pushed:<2013-02``, ``pushed:>=2013-03-06`` - ``user`` or ``repo`` Limits searches to a specific user or repository. - ``language`` Searches repositories based on the language they're written in. - ``stars`` Searches repositories based on the number of stars. For more information about these qualifiers, see: http://git.io/4Z8AkA :param str query: (required), a valid query as described above, e.g., ``tetris language:assembly`` :param str sort: (optional), how the results should be sorted; options: ``stars``, ``forks``, ``updated``; default: best match :param str order: (optional), the direction of the sorted results, options: ``asc``, ``desc``; default: ``desc`` :param int per_page: (optional) :param bool text_match: (optional), if True, return matching search terms. See http://git.io/4ct1eQ for more information :param int number: (optional), number of repositories to return. Default: -1, returns all available repositories :param str etag: (optional), previous ETag header value :return: generator of :class:`Repository ` """ params = {'q': query} headers = {} if sort in ('stars', 'forks', 'updated'): params['sort'] = sort if order in ('asc', 'desc'): params['order'] = order if text_match: headers = { 'Accept': 'application/vnd.github.v3.full.text-match+json' } url = self._build_url('search', 'repositories') return SearchIterator(number, url, RepositorySearchResult, self, params, etag, headers) def search_users(self, query, sort=None, order=None, per_page=None, text_match=False, number=-1, etag=None): """Find users via the Search API. The query can contain any combination of the following supported qualifers: - ``type`` With this qualifier you can restrict the search to just personal accounts or just organization accounts. - ``in`` Qualifies which fields are searched. With this qualifier you can restrict the search to just the username, public email, full name, or any combination of these. - ``repos`` Filters users based on the number of repositories they have. - ``location`` Filter users by the location indicated in their profile. - ``language`` Search for users that have repositories that match a certain language. - ``created`` Filter users based on when they joined. - ``followers`` Filter users based on the number of followers they have. For more information about these qualifiers see: http://git.io/wjVYJw :param str query: (required), a valid query as described above, e.g., ``tom repos:>42 followers:>1000`` :param str sort: (optional), how the results should be sorted; options: ``followers``, ``repositories``, or ``joined``; default: best match :param str order: (optional), the direction of the sorted results, options: ``asc``, ``desc``; default: ``desc`` :param int per_page: (optional) :param bool text_match: (optional), if True, return matching search terms. See http://git.io/_V1zRwa for more information :param int number: (optional), number of search results to return; Default: -1 returns all available :param str etag: (optional), ETag header value of the last request. :return: generator of :class:`UserSearchResult ` """ params = {'q': query} headers = {} if sort in ('followers', 'repositories', 'joined'): params['sort'] = sort if order in ('asc', 'desc'): params['order'] = order if text_match: headers = { 'Accept': 'application/vnd.github.v3.full.text-match+json' } url = self._build_url('search', 'users') return SearchIterator(number, url, UserSearchResult, self, params, etag, headers) def set_client_id(self, id, secret): """Allows the developer to set their client_id and client_secret for their OAuth application. :param str id: 20-character hexidecimal client_id provided by GitHub :param str secret: 40-character hexidecimal client_secret provided by GitHub """ self.session.params = {'client_id': id, 'client_secret': secret} def set_user_agent(self, user_agent): """Allows the user to set their own user agent string to identify with the API. :param str user_agent: String used to identify your application. Library default: "github3.py/{version}", e.g., "github3.py/0.5" """ if not user_agent: return self.session.headers.update({'User-Agent': user_agent}) @requires_auth def star(self, username, repo): """Star to username/repo :param str username: (required), owner of the repo :param str repo: (required), name of the repo :return: bool """ resp = False if username and repo: url = self._build_url('user', 'starred', username, repo) resp = self._boolean(self._put(url), 204, 404) return resp @requires_auth def starred(self, sort=None, direction=None, number=-1, etag=None): """Iterate over repositories starred by the authenticated user. .. versionchanged:: 1.0 This was split from ``iter_starred`` and requires authentication. :param str sort: (optional), either 'created' (when the star was created) or 'updated' (when the repository was last pushed to) :param str direction: (optional), either 'asc' or 'desc'. Default: 'desc' :param int number: (optional), number of repositories to return. Default: -1 returns all repositories :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Repository ` """ params = {'sort': sort, 'direction': direction} self._remove_none(params) url = self._build_url('user', 'starred') return self._iter(int(number), url, Repository, params, etag) def starred_by(self, username, sort=None, direction=None, number=-1, etag=None): """Iterate over repositories starred by ``username``. .. versionadded:: 1.0 This was split from ``iter_starred`` and requires the login parameter. :param str username: name of user whose stars you want to see :param str sort: (optional), either 'created' (when the star was created) or 'updated' (when the repository was last pushed to) :param str direction: (optional), either 'asc' or 'desc'. Default: 'desc' :param int number: (optional), number of repositories to return. Default: -1 returns all repositories :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Repository ` """ params = {'sort': sort, 'direction': direction} self._remove_none(params) url = self._build_url('users', str(username), 'starred') return self._iter(int(number), url, Repository, params, etag) @requires_auth def subscriptions(self, number=-1, etag=None): """Iterate over repositories subscribed to by the authenticated user. :param int number: (optional), number of repositories to return. Default: -1 returns all repositories :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Repository ` """ url = self._build_url('user', 'subscriptions') return self._iter(int(number), url, Repository, etag=etag) def subscriptions_for(self, username, number=-1, etag=None): """Iterate over repositories subscribed to by ``username``. :param str username: , name of user whose subscriptions you want to see :param int number: (optional), number of repositories to return. Default: -1 returns all repositories :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Repository ` """ url = self._build_url('users', str(username), 'subscriptions') return self._iter(int(number), url, Repository, etag=etag) @requires_auth def unfollow(self, username): """Make the authenticated user stop following username :param str username: (required) :returns: bool """ resp = False if username: url = self._build_url('user', 'following', username) resp = self._boolean(self._delete(url), 204, 404) return resp @requires_auth def unstar(self, username, repo): """Unstar username/repo. :param str username: (required), owner of the repo :param str repo: (required), name of the repo :return: bool """ resp = False if username and repo: url = self._build_url('user', 'starred', username, repo) resp = self._boolean(self._delete(url), 204, 404) return resp @requires_auth def update_me(self, name=None, email=None, blog=None, company=None, location=None, hireable=False, bio=None): """Update the profile of the authenticated user. :param str name: e.g., 'John Smith', not login name :param str email: e.g., 'john.smith@example.com' :param str blog: e.g., 'http://www.example.com/jsmith/blog' :param str company: :param str location: :param bool hireable: defaults to False :param str bio: GitHub flavored markdown :returns: whether the operation was successful or not :rtype: bool """ user = {'name': name, 'email': email, 'blog': blog, 'company': company, 'location': location, 'hireable': hireable, 'bio': bio} self._remove_none(user) url = self._build_url('user') _json = self._json(self._patch(url, data=json.dumps(user)), 200) if _json: self._update_attributes(_json) return True return False def user(self, username): """Returns a User object for the specified user name. :param str username: name of the user :returns: :class:`User ` """ url = self._build_url('users', username) json = self._json(self._get(url), 200) return self._instance_or_null(users.User, json) @requires_auth def user_issues(self, filter='', state='', labels='', sort='', direction='', since=None, per_page=None, number=-1, etag=None): """List only the authenticated user's issues. Will not list organization's issues .. versionchanged:: 1.0 ``per_page`` parameter added before ``number`` .. versionchanged:: 0.9.0 - The ``state`` parameter now accepts 'all' in addition to 'open' and 'closed'. :param str filter: accepted values: ('assigned', 'created', 'mentioned', 'subscribed') api-default: 'assigned' :param str state: accepted values: ('all', 'open', 'closed') api-default: 'open' :param str labels: comma-separated list of label names, e.g., 'bug,ui,@high' :param str sort: accepted values: ('created', 'updated', 'comments') api-default: created :param str direction: accepted values: ('asc', 'desc') api-default: desc :param since: (optional), Only issues after this date will be returned. This can be a `datetime` or an ISO8601 formatted date string, e.g., 2012-05-20T23:10:27Z :type since: datetime or string :param int number: (optional), number of issues to return. Default: -1 returns all issues :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Issue ` """ url = self._build_url('user', 'issues') # issue_params will handle the since parameter params = issue_params(filter, state, labels, sort, direction, since) params.update(per_page=per_page) return self._iter(int(number), url, Issue, params, etag) @requires_auth def user_teams(self, number=-1, etag=None): """Gets the authenticated user's teams across all of organizations. List all of the teams across all of the organizations to which the authenticated user belongs. This method requires user or repo scope when authenticating via OAuth. :returns: generator of :class:`Team ` objects """ url = self._build_url('user', 'teams') return self._iter(int(number), url, Team, etag=etag) def user_with_id(self, number): """Get the user's information with id ``number``. :param int number: the user's id number :returns: :class:`User ` """ number = int(number) json = None if number > 0: url = self._build_url('user', str(number)) json = self._json(self._get(url), 200) return self._instance_or_null(users.User, json) def zen(self): """Returns a quote from the Zen of GitHub. Yet another API Easter Egg :returns: str (on Python 3, unicode on Python 2) """ url = self._build_url('zen') resp = self._get(url) return resp.text if resp.status_code == 200 else b''.decode('utf-8') class GitHubEnterprise(GitHub): """For GitHub Enterprise users, this object will act as the public API to your instance. You must provide the URL to your instance upon initialization and can provide the rest of the login details just like in the :class:`GitHub ` object. There is no need to provide the end of the url (e.g., /api/v3/), that will be taken care of by us. If you have a self signed SSL for your local github enterprise you can override the validation by passing `verify=False`. """ def __init__(self, url, username='', password='', token='', verify=True): super(GitHubEnterprise, self).__init__(username, password, token) self.session.base_url = url.rstrip('/') + '/api/v3' self.session.verify = verify self.url = url def _repr(self): return ''.format(self) @requires_auth def create_user(self, login, email): """Create a new user. This is only available for administrators of the instance. :param str login: (required), The user's username. :param str email: (required), The user's email address. :returns: :class:`User `, if successful """ url = self._build_url('admin', 'users') payload = {'login': login, 'email': email} json_data = self._json(self._post(url, data=payload), 201) return self._instance_or_null(users.User, json_data) @requires_auth def admin_stats(self, option): """This is a simple way to get statistics about your system. :param str option: (required), accepted values: ('all', 'repos', 'hooks', 'pages', 'orgs', 'users', 'pulls', 'issues', 'milestones', 'gists', 'comments') :returns: dict """ stats = {} if option.lower() in ('all', 'repos', 'hooks', 'pages', 'orgs', 'users', 'pulls', 'issues', 'milestones', 'gists', 'comments'): url = self._build_url('enterprise', 'stats', option.lower()) stats = self._json(self._get(url), 200) return stats class GitHubStatus(GitHubCore): """A sleek interface to the GitHub System Status API. This will only ever return the JSON objects returned by the API. """ def __init__(self): super(GitHubStatus, self).__init__({}) self.session.base_url = 'https://status.github.com' def _repr(self): return '' def _recipe(self, *args): url = self._build_url(*args) resp = self._get(url) return resp.json() if self._boolean(resp, 200, 404) else {} def api(self): """GET /api.json""" return self._recipe('api.json') def status(self): """GET /api/status.json""" return self._recipe('api', 'status.json') def last_message(self): """GET /api/last-message.json""" return self._recipe('api', 'last-message.json') def messages(self): """GET /api/messages.json""" return self._recipe('api', 'messages.json') gitsome-0.8.0/gitsome/lib/github3/issues/000077500000000000000000000000001345243314000202625ustar00rootroot00000000000000gitsome-0.8.0/gitsome/lib/github3/issues/__init__.py000066400000000000000000000014521345243314000223750ustar00rootroot00000000000000""" github3.issues ============== This module contains the classes related to issues. See also: http://developer.github.com/v3/issues/ """ from ..utils import timestamp_parameter from .issue import Issue __all__ = [Issue] def issue_params(filter, state, labels, sort, direction, since): params = {} if filter in ('assigned', 'created', 'mentioned', 'subscribed', 'all'): params['filter'] = filter if state in ('open', 'closed', 'all'): params['state'] = state if labels: params['labels'] = labels if sort in ('created', 'updated', 'comments'): params['sort'] = sort if direction in ('asc', 'desc'): params['direction'] = direction since = timestamp_parameter(since) if since: params['since'] = since return params gitsome-0.8.0/gitsome/lib/github3/issues/comment.py000066400000000000000000000025701345243314000223020ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from ..utils import timestamp_parameter from ..models import BaseComment from ..users import User class IssueComment(BaseComment): """The :class:`IssueComment ` object. This structures and handles the comments on issues specifically. Two comment instances can be checked like so:: c1 == c2 c1 != c2 And is equivalent to:: c1.id == c2.id c1.id != c2.id See also: http://developer.github.com/v3/issues/comments/ """ def _update_attributes(self, comment): super(IssueComment, self)._update_attributes(comment) user = comment.get('user') #: :class:`User ` who made the comment self.user = User(user, self) if user else None #: Issue url (not a template) self.issue_url = comment.get('issue_url') #: Html url (not a template) self.html_url = comment.get('html_url') def _repr(self): return ''.format(self.user.login) def issue_comment_params(sort, direction, since): params = {} if sort in ('created', 'updated'): params['sort'] = sort if direction in ('asc', 'desc'): params['direction'] = direction since = timestamp_parameter(since) if since: params['since'] = since return params gitsome-0.8.0/gitsome/lib/github3/issues/event.py000066400000000000000000000050311345243314000217540ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from ..models import GitHubCore from ..users import User class IssueEvent(GitHubCore): """The :class:`IssueEvent ` object. This specifically deals with events described in the `Issues\>Events `_ section of the GitHub API. Two event instances can be checked like so:: e1 == e2 e1 != e2 And is equivalent to:: e1.commit_id == e2.commit_id e1.commit_id != e2.commit_id """ def _update_attributes(self, event): # The type of event: # ('closed', 'reopened', 'subscribed', 'merged', 'referenced', # 'mentioned', 'assigned') #: The type of event, e.g., closed self.event = event.get('event') #: SHA of the commit. self.commit_id = event.get('commit_id') self._api = event.get('url', '') #: :class:`Issue ` where this comment was made. self.issue = event.get('issue') if self.issue: from .issue import Issue self.issue = Issue(self.issue, self) #: :class:`User ` who caused this event. self.actor = event.get('actor') if self.actor: self.actor = User(self.actor, self) #: :class:`User ` that generated the event. self.actor = event.get('actor') if self.actor: self.actor = User(self.actor, self) #: Number of comments self.comments = event.get('comments', 0) #: datetime object representing when the event was created. self.created_at = self._strptime(event.get('created_at')) #: Dictionary of links for the pull request self.pull_request = event.get('pull_request', {}) #: Dictionary containing label details self.label = event.get('label', {}) #: The integer ID of the event self.id = event.get('id') #: :class:`User ` that is assigned self.assignee = event.get('assignee') if self.assignee: self.assignee = User(self.assignee, self) #: Dictionary containing milestone details self.milestone = event.get('milestone', {}) #: Dictionary containing to and from attributes self.rename = event.get('rename', {}) self._uniq = self.commit_id def _repr(self): return ''.format( self.event, self.actor ) gitsome-0.8.0/gitsome/lib/github3/issues/issue.py000066400000000000000000000311151345243314000217650ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from re import match from json import dumps from ..decorators import requires_auth from .comment import IssueComment, issue_comment_params from .event import IssueEvent from .label import Label from .milestone import Milestone from ..models import GitHubCore from ..users import User from uritemplate import URITemplate class Issue(GitHubCore): """The :class:`Issue ` object. It structures and handles the data returned via the `Issues `_ section of the GitHub API. Two issue instances can be checked like so:: i1 == i2 i1 != i2 And is equivalent to:: i1.id == i2.id i1.id != i2.id """ # The Accept header will likely be removable once the feature is out of # preview mode. See: https://git.io/vgXmB LOCKING_PREVIEW_HEADERS = { 'Accept': 'application/vnd.github.the-key-preview+json' } def _update_attributes(self, issue): self._api = issue.get('url', '') #: :class:`User ` representing the user the issue #: was assigned to. self.assignee = issue.get('assignee') if self.assignee: self.assignee = User(issue.get('assignee'), self) #: Body (description) of the issue. self.body = issue.get('body', '') #: HTML formatted body of the issue. self.body_html = issue.get('body_html', '') #: Plain text formatted body of the issue. self.body_text = issue.get('body_text', '') # If an issue is still open, this field will be None #: datetime object representing when the issue was closed. self.closed_at = self._strptime(issue.get('closed_at')) #: Number of comments on this issue. self.comments_count = issue.get('comments') #: Comments url (not a template) self.comments_url = issue.get('comments_url') #: datetime object representing when the issue was created. self.created_at = self._strptime(issue.get('created_at')) #: Events url (not a template) self.events_url = issue.get('events_url') #: URL to view the issue at GitHub. self.html_url = issue.get('html_url') #: Unique ID for the issue. self.id = issue.get('id') #: Returns the list of :class:`Label `\ s #: on this issue. self.original_labels = [ Label(l, self) for l in issue.get('labels') ] labels_url = issue.get('labels_url') #: Labels URL Template. Expand with ``name`` self.labels_urlt = URITemplate(labels_url) if labels_url else None #: Locked status self.locked = issue.get('locked') #: :class:`Milestone ` this #: issue was assigned to. self.milestone = None if issue.get('milestone'): self.milestone = Milestone(issue.get('milestone'), self) #: Issue number (e.g. #15) self.number = issue.get('number') #: Dictionary URLs for the pull request (if they exist) self.pull_request_urls = issue.get('pull_request', {}) m = match('https?://[\w\d\-\.\:]+/(\S+)/(\S+)/(?:issues|pull)/\d+', self.html_url) #: Returns ('owner', 'repository') this issue was filed on. self.repository = m.groups() #: State of the issue, e.g., open, closed self.state = issue.get('state') #: Title of the issue. self.title = issue.get('title') #: datetime object representing the last time the issue was updated. self.updated_at = self._strptime(issue.get('updated_at')) #: :class:`User ` who opened the issue. self.user = User(issue.get('user'), self) closed_by = issue.get('closed_by') #: :class:`User ` who closed the issue. self.closed_by = User(closed_by, self) if closed_by else None def _repr(self): return ''.format(r=self.repository, n=self.number) @requires_auth def add_labels(self, *args): """Add labels to this issue. :param str args: (required), names of the labels you wish to add :returns: list of :class:`Label`\ s """ url = self._build_url('labels', base_url=self._api) json = self._json(self._post(url, data=args), 200) return [Label(l, self) for l in json] if json else [] @requires_auth def assign(self, username): """Assigns user ``username`` to this issue. This is a short cut for ``issue.edit``. :param str username: username of the person to assign this issue to :returns: bool """ if not username: return False number = self.milestone.number if self.milestone else None labels = [str(l) for l in self.original_labels] return self.edit(self.title, self.body, username, self.state, number, labels) @requires_auth def close(self): """Close this issue. :returns: bool """ assignee = self.assignee.login if self.assignee else '' number = self.milestone.number if self.milestone else None labels = [str(l) for l in self.original_labels] return self.edit(self.title, self.body, assignee, 'closed', number, labels) def comment(self, id_num): """Get a single comment by its id. The catch here is that id is NOT a simple number to obtain. If you were to look at the comments on issue #15 in sigmavirus24/Todo.txt-python, the first comment's id is 4150787. :param int id_num: (required), comment id, see example above :returns: :class:`IssueComment ` """ json = None if int(id_num) > 0: # Might as well check that it's positive owner, repo = self.repository url = self._build_url('repos', owner, repo, 'issues', 'comments', str(id_num)) json = self._json(self._get(url), 200) return self._instance_or_null(IssueComment, json) def comments(self, number=-1, sort='', direction='', since=None): """Iterate over the comments on this issue. :param int number: (optional), number of comments to iterate over Default: -1 returns all comments :param str sort: accepted valuees: ('created', 'updated') api-default: created :param str direction: accepted values: ('asc', 'desc') Ignored without the sort parameter :param since: (optional), Only issues after this date will be returned. This can be a `datetime` or an ISO8601 formatted date string, e.g., 2012-05-20T23:10:27Z :type since: datetime or string :returns: iterator of :class:`IssueComment `\ s """ url = self._build_url('comments', base_url=self._api) params = issue_comment_params(sort, direction, since) return self._iter(int(number), url, IssueComment, params) @requires_auth def create_comment(self, body): """Create a comment on this issue. :param str body: (required), comment body :returns: :class:`IssueComment ` """ json = None if body: url = self._build_url('comments', base_url=self._api) json = self._json(self._post(url, data={'body': body}), 201) return self._instance_or_null(IssueComment, json) @requires_auth def edit(self, title=None, body=None, assignee=None, state=None, milestone=None, labels=None): """Edit this issue. :param str title: Title of the issue :param str body: markdown formatted body (description) of the issue :param str assignee: login name of user the issue should be assigned to :param str state: accepted values: ('open', 'closed') :param int milestone: the NUMBER (not title) of the milestone to assign this to [1]_, or 0 to remove the milestone :param list labels: list of labels to apply this to :returns: bool .. [1] Milestone numbering starts at 1, i.e. the first milestone you create is 1, the second is 2, etc. """ json = None data = {'title': title, 'body': body, 'assignee': assignee, 'state': state, 'milestone': milestone, 'labels': labels} self._remove_none(data) if data: if 'milestone' in data and data['milestone'] == 0: data['milestone'] = None json = self._json(self._patch(self._api, data=dumps(data)), 200) if json: self._update_attributes(json) return True return False def events(self, number=-1): """Iterate over events associated with this issue only. :param int number: (optional), number of events to return. Default: -1 returns all events available. :returns: generator of :class:`IssueEvent `\ s """ url = self._build_url('events', base_url=self._api) return self._iter(int(number), url, IssueEvent) def is_closed(self): """Checks if the issue is closed. :returns: bool """ if self.closed_at or (self.state == 'closed'): return True return False def labels(self, number=-1, etag=None): """Iterate over the labels associated with this issue. :param int number: (optional), number of labels to return. Default: -1 returns all labels applied to this issue. :param str etag: (optional), ETag from a previous request to the same endpoint :returns: generator of :class:`Label `\ s """ url = self._build_url('labels', base_url=self._api) return self._iter(int(number), url, Label, etag=etag) @requires_auth def lock(self): """Lock an issue. :returns: bool """ headers = Issue.LOCKING_PREVIEW_HEADERS url = self._build_url('lock', base_url=self._api) return self._boolean(self._put(url, headers=headers), 204, 404) def pull_request(self): """Retrieve the pull request associated with this issue. :returns: :class:`~github3.pulls.PullRequest` """ from .. import pulls json = None pull_request_url = self.pull_request_urls.get('url') if pull_request_url: json = self._json(self._get(pull_request_url), 200) return self._instance_or_null(pulls.PullRequest, json) @requires_auth def remove_label(self, name): """Removes label ``name`` from this issue. :param str name: (required), name of the label to remove :returns: list of :class:`Label` """ url = self._build_url('labels', name, base_url=self._api) json = self._json(self._delete(url), 200, 404) labels = [Label(label, self) for label in json] if json else [] return labels @requires_auth def remove_all_labels(self): """Remove all labels from this issue. :returns: an empty list if successful """ # Can either send DELETE or [] to remove all labels return self.replace_labels([]) @requires_auth def replace_labels(self, labels): """Replace all labels on this issue with ``labels``. :param list labels: label names :returns: list of :class:`Label` """ url = self._build_url('labels', base_url=self._api) json = self._json(self._put(url, data=dumps(labels)), 200) return [Label(l, self) for l in json] if json else [] @requires_auth def reopen(self): """Re-open a closed issue. :returns: bool """ assignee = self.assignee.login if self.assignee else '' number = self.milestone.number if self.milestone else None labels = [str(l) for l in self.original_labels] return self.edit(self.title, self.body, assignee, 'open', number, labels) @requires_auth def unlock(self): """Unlock an issue. :returns: bool """ headers = Issue.LOCKING_PREVIEW_HEADERS url = self._build_url('lock', base_url=self._api) return self._boolean(self._delete(url, headers=headers), 204, 404) gitsome-0.8.0/gitsome/lib/github3/issues/label.py000066400000000000000000000027631345243314000217230ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from json import dumps from ..decorators import requires_auth from ..models import GitHubCore class Label(GitHubCore): """The :class:`Label