From 9c63eeefd838f63e4111d0b3d0aa6f97c3864404 Mon Sep 17 00:00:00 2001 From: Jacopo Notarstefano Date: Mon, 16 Jul 2018 00:30:44 +0200 Subject: [PATCH] cli: add working update command Sem-ver: adds new feature --- .travis.yml | 5 + README.rst | 24 +++- github_file/cli.py | 42 +++++- setup.py | 4 + ...est_update_uses_the_provided_filename.yaml | 136 ++++++++++++++++++ tests/test_cli.py | 22 ++- 6 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 tests/cassettes/test_update_uses_the_provided_filename.yaml diff --git a/.travis.yml b/.travis.yml index 1a269c8..a44f9c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,11 @@ notifications: sudo: false +env: + global: + - GITHUB_USER=username + - GITHUB_PASS=password + language: python cache: pip diff --git a/README.rst b/README.rst index c231b54..fefac82 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,29 @@ Currently ``github-file`` implements one subcommand: This will update the configuration of your repository on GitHub so that it matches what is described in a file called (by default) ``Githubfile`` in the -``.github`` folder. +``.github`` folder. Here's an example of such a file: + +.. code-block:: ini + + [core] + owner = jacquerie + repo = github-file + description = Configure your GitHub repository from a file, + without having to click around in the UI. + + [features] + has_issues = true + has_wiki = false + +The meaning of these options is explained in GitHub's API documentation at +https://developer.github.com/v3/repos/#edit, although not all options are +currently available. Here's what's currently configurable: + +- ``description`` +- ``homepage`` +- ``private`` +- ``has_issues`` +- ``has_wiki`` Author diff --git a/github_file/cli.py b/github_file/cli.py index e5c81d3..dfaf6b2 100644 --- a/github_file/cli.py +++ b/github_file/cli.py @@ -5,6 +5,9 @@ import os import click +from dotenv import find_dotenv, load_dotenv +from github3 import GitHub +from six.moves import configparser DEFAULT_FILENAME = os.path.join('.github', 'Githubfile') @@ -12,10 +15,47 @@ @click.group() @click.version_option() def cli(): - pass + load_dotenv(find_dotenv()) @cli.command() @click.option('-f', '--file', 'filename', default=DEFAULT_FILENAME) def update(filename): """Update the repo to match the config file.""" + parser = configparser.ConfigParser() + parser.read(filename) + + owner = parser.get('core', 'owner') + repo = parser.get('core', 'repo') + + description = '' + if parser.has_option('core', 'description'): + description = ' '.join(parser.get('core', 'description').split()) + homepage = '' + if parser.has_option('core', 'homepage'): + homepage = parser.get('core', 'homepage') + private = False + if parser.has_option('core', 'private'): + private = parser.getboolean('core', 'private') + + has_issues = False + if parser.has_option('features', 'has_issues'): + has_issues = parser.getboolean('features', 'has_issues') + has_wiki = False + if parser.has_option('features', 'has_wiki'): + has_wiki = parser.getboolean('features', 'has_wiki') + + config = { + 'description': description, + 'homepage': homepage, + 'private': private, + 'has_issues': has_issues, + 'has_wiki': has_wiki, + } + + github_user = os.getenv('GITHUB_USER') + github_pass = os.getenv('GITHUB_PASS') + github = GitHub(github_user, github_pass) + + repository = github.repository(owner, repo) + repository.edit(repo, **config) diff --git a/setup.py b/setup.py index ce1c875..5897bf2 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,9 @@ install_requires = [ 'click~=6.0,>=6.7', + 'github3.py~=1.0,>=1.1.0', + 'python-dotenv~=0.0,>=0.8.2', + 'six~=1.0,>=1.11.0', ] docs_require = [] @@ -24,6 +27,7 @@ tests_require = [ 'flake8-future-import~=0.0,>=0.4.4', 'pytest-cov~=2.0,>=2.5.1', + 'pytest-vcr~=0.0,>=0.3.0', 'pytest~=3.0,>=3.2.3', ] diff --git a/tests/cassettes/test_update_uses_the_provided_filename.yaml b/tests/cassettes/test_update_uses_the_provided_filename.yaml new file mode 100644 index 0000000..2c9cd93 --- /dev/null +++ b/tests/cassettes/test_update_uses_the_provided_filename.yaml @@ -0,0 +1,136 @@ +interactions: +- request: + body: null + headers: + Accept: [application/vnd.github.v3.full+json] + Accept-Charset: [utf-8] + Accept-Encoding: ['gzip, deflate'] + Authorization: [Basic dXNlcm5hbWU6cGFzc3dvcmQ=] + Connection: [keep-alive] + Content-Type: [application/json] + User-Agent: [github3.py/1.1.0] + method: GET + uri: https://api.github.com/repos/jacquerie/github-file + response: + body: + string: !!binary | + H4sIAAAAAAAAA6WYUW/iOBDHv0rE61FCunSvRar2VrfVXk/X7t6JvVv1BZnEELeJnbUdWIj63e9v + O5AEVdDiFwTG8/N4PDOecdVjSW8cjYYXV9HoIur3uEjo1Iz17j7drL5kf2bx56sN+f7PMuZPP+8/ + fYy+TG6G95Ob6x4mk5xi5oLptJydzVlGMTgvs2xa//NI4h8llYyG3TlixansjateJhaMA7GbCIBZ + /d1ldH457Krz9/t/v99n8ePd6G5yO7r7eG1UIEuiiZyWMgMl1bpQ4zB0g+p84FYtFZWx4JpyPYhF + Hpahw39YXo+AWMgaYreNgT1YwWqOEwZMhW19U51newq4de389sy5yDKxgvy+vgeXCHdixroWwfji + FATEqlDolMJg2Maz2TxT+o3qWJEKJ6o0PMVAFI5A0uRtKtVCUMg4w3MVSloISytnKpas0EzwN6rW + EQVKyAXhbENOQEFUgWCUeqMSVgSidAmHe6Osk6nCQrIlidfGHJLGlC1h3VN4e8LA6XVhYvYbzt/Y + mmk6JUlugnBOMkWf+z27tsYkO9BHVL3Kv/dCPKG7Q8Ryvws+Z4tS0mAtShl8ZvqPchZYSzEt5DqY + S5EHJDA5pB+skFBEqYOULOGygRZBnLH4KSBSlDwJGA/gw8G32wF2MBfyaafqwci1qzXxuKev4Rw5 + rAMAxCjEoc4TXXtQjHQV4rMOrBjRTmZCEtjIA9vBVGH7p/EwTUnuQbfiwKRC+FjQigPDlCrpq5z9 + 0HlYigq38cTLfOYS3mui6BDYyUNPohRbcEo9LLdDVOE2H88k4XHqA90SqtB9sydMFh5qGmlAZpmY + eVBwJ4YWUYUqJe7u0VM/zQzTEDpISeeeahrCDqml1xlbFQ1iB8TFp3HcHjpuCWFVWzIjfFGShQ9z + h8BJm6t5QTZHC5VDcdIwADTVl2Sz0jeNNRSjpasTENc+pmwgDdKWHoermYNbbxUwdvN5zo4VAod4 + NaDj5N5Q45f7YPP7eM1yTFVDqMIm47qEXrNPt2qd0bc6tleoi3sPN9gSwuqXgujUZCcsVBBJT1e4 + BoTVjKCuGgwGVUqJrZdzKr1i1ckDRGScojY8XcdqS0D9khNtq/C5UTFBVZ4JknjYdIcAzh3e6Xo6 + +faZF2gzPZSz4m1ejspTacF9cmjDaJO50GzO4tc0IYdCq4OpPijGY9onWdaHl2oWM/gtimVzdigZ + qY9tnDy2gPbedSAZhQt7WFtSR6hC1zAmtMjE2jPXtCAmXCVF05JMiUa7cT6MLs+Gv55F0SS6GA+j + 8Wj4gDllkbww52JyHo3P342HV2ZOUar0BQz+301B6qw9Gd/w3IBPvH90u/12d2GeD0BWKm3EfmuE + xi++ktRCcQaX3Iub16633L/DjglCyVTktEAl4V5CFNvg20WnIojRg8HGeJxZEY1SFbdvM7StIiD+ + dY0mjhskUVMXv72xlqXpKDFSSPFIY63aY03GaE1csSfW9KJG0hQ6uxHXtzUa5ExKUT8IcQT5Lkni + caduaEVBea1RW3UWU66w3co0cdgALm1oX79k3d1Ogr/qGTBHkfysX8luJ8axus9P3ecctK4GrMIa + 2Hphi99PPmePD/9dbB4mN5semm/XRxrrtrTsWNv+SOiclJmeuhLfKEuUth19QWUOa5u3E7OVurd3 + dje+vbW3SYDuO1ZFGhGrqfpREviovV2209w/dgi2MoVM9x9Jze3WleFUr9AOt2zbLtXqo4qe/wc+ + aoEGfBQAAA== + headers: + access-control-allow-origin: ['*'] + access-control-expose-headers: ['ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, + X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, + X-Poll-Interval'] + cache-control: ['private, max-age=60, s-maxage=60'] + content-encoding: [gzip] + content-security-policy: [default-src 'none'] + content-type: [application/json; charset=utf-8] + date: ['Sun, 15 Jul 2018 22:25:30 GMT'] + etag: [W/"497305d747d01b0c563b8f38a552cf10"] + last-modified: ['Sun, 15 Jul 2018 21:23:09 GMT'] + referrer-policy: ['origin-when-cross-origin, strict-origin-when-cross-origin'] + server: [GitHub.com] + status: [200 OK] + strict-transport-security: [max-age=31536000; includeSubdomains; preload] + vary: ['Accept, Authorization, Cookie, X-GitHub-OTP'] + x-content-type-options: [nosniff] + x-frame-options: [deny] + x-github-media-type: [github.v3; param=full; format=json] + x-github-request-id: ['8982:7DB6:A098CBC:147496BE:5B4BC9D9'] + x-ratelimit-limit: ['5000'] + x-ratelimit-remaining: ['4999'] + x-ratelimit-reset: ['1531697130'] + x-runtime-rack: ['0.082371'] + x-xss-protection: [1; mode=block] + status: {code: 200, message: OK} +- request: + body: !!python/unicode '{"has_wiki": false, "name": "github-file", "has_issues": + true, "homepage": "", "private": false, "description": "Configure your GitHub + repository from a file, without having to click around in the UI."}' + headers: + Accept: [application/vnd.github.v3.full+json] + Accept-Charset: [utf-8] + Accept-Encoding: ['gzip, deflate'] + Authorization: [Basic dXNlcm5hbWU6cGFzc3dvcmQ=] + Connection: [keep-alive] + Content-Length: ['202'] + Content-Type: [application/json] + User-Agent: [github3.py/1.1.0] + method: PATCH + uri: https://api.github.com/repos/jacquerie/github-file + response: + body: + string: !!binary | + H4sIAAAAAAAAA6WYYW/iOBCG/0rE16MNUNhrkaq91W3V6+na3Tuxd6t+QSYxxG1iZ20HFqL+93tt + B5KgClosVRUYz+PxeGY847LD4s64P+yNrvrDUb/b4SKmUzPWuf98s/qS/plGt1cb8v2fZcSffz58 + /tT/MrnpPUxurjuYTDKKmQumk2J2NmcpxeC8SNNp9csTiX4UVDIatueIFaeyMy47qVgwDsRuIgBm + 9YvL/uCy11bn7w//fn9Io6f74f3kbnj/6dqoQJZEEzktZApKonWuxmHoBtXg3K1aKCojwTXl+jwS + WViEDv9xeT0EYiEriN02BvZgOas4ThgwFTb1TXSW7ing1rXzmzPnIk3FCvL7+h5cItyJGetaBOOL + UxAQK0OhEwqDYRsvZvNM6XeqY0VKnKjS8BQDUTgCSeP3qVQJQSHjDC9lKGkuLK2YqUiyXDPB36la + SxQoIReEsw05AQVRBYJR6p1KWBGI0iUc7p2yTqYMc8mWJFobc0gaUbaEdU/h7QkDp9e5idlvOH9j + a6bplMSZCcI5SRV96Xbs2hqT7EAXUfUm/94L8ZjuDhHL/S74nC0KSYO1KGRwy/QfxSywlmJayHUw + lyILSGBySDdYIaGIQgcJWcJlAy2CKGXRc0CkKHgcMB7Ah4Nvd+fYwVzI552qByPXrlbH456+hnPk + sA4AEKMQhzrPdO1BMdJliP9VYEWIdjITksBGHtgWpgybX42HaUoyD7oVByYRwseCVhwYplRB3+Ts + h87DUlS4jSdeZDOX8N4SRYfATh56EqXYglPqYbkdogy3+XgmCY8SH+iWUIbukz1hsvBQ00gDMkvF + zIOCOzG0iDJUCXF3j576aWaYhtBCSjr3VNMQdkgtvc7YqmgQOyAuPo3j9tBxSwjLypIp4YuCLHyY + OwRO2lzNC7I5WqgcipOaAaCpviSbFb5prKYYLV2dgLj2MWUNqZG29DhczRzceqOAsZvPMnasEDjE + qwAtJ/eGGr/cB5vvx2uWY6oaQhnWGdcl9Ip9ulWrjL7VsblCVdx7uMGWEJa/5EQnJjthoZxIerrC + FSAsZwR11fn5eZlQYuvljEqvWHXyABEZJagNT9ex3BJQv2RE2yp8blSMUZWngsQeNt0hgHOHd7qe + Tr555jnaTA/lrHiTl6HyVFpwnxxaM5pkLjSbs+gtTcih0Gphyo+K8Yh2SZp24aWaRQx+i2LZnB1K + RupjGyePLaC9dx1ISuHCHtaW1BHK0DWMMc1TsfbMNQ2ICVdJ0bTEU6LRbgx6/cuz3q9n/f6kPxr3 + +uNh7xFzijx+Zc5oMhiMB6PxhZ2TFyp5BTO4GOOvd2WmIHVWnoxPeG7Af7x/tLv9Zndhng8gplRS + i/1WC41ffSWphKIULrkXN29db7l/hx0ThJKJyGiOSsK9hCi2wadRqyKI0IPBxnicWRGNUhW3bz20 + rSIg/nWNJo4bJFFTF7+dsZaF6SgxkkvxRCOtmmN1xmhMXLFnVveiRtIUOrsR17fVGmRMSlE9CHEE + +S5J4nGnamhFTnmlUVN1FlGusN3SNHHYAC5taF+9ZN3fTYK/qhkwRx7/rF7J7ibGsdrPT+3nHLSu + BqzCCth4YYs+TG7Tp8f/RpvHyc2mg+bb9ZHGug0tW9a2X2I6J0Wqp67EN8oSpW1Hn1OZwdrm7cRs + pertnd2Nb2/tbRKg+4xVkUbEaqp+FAQ+am+X7TT3ix2CrUwh0/5FUnO7tWU41Su0ww3bNku16qj6 + L/8Dr1WYmXwUAAA= + headers: + access-control-allow-origin: ['*'] + access-control-expose-headers: ['ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, + X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, + X-Poll-Interval'] + cache-control: ['private, max-age=60, s-maxage=60'] + content-encoding: [gzip] + content-security-policy: [default-src 'none'] + content-type: [application/json; charset=utf-8] + date: ['Sun, 15 Jul 2018 22:25:30 GMT'] + etag: [W/"4e2c4803d93792997d5710f373e899cd"] + referrer-policy: ['origin-when-cross-origin, strict-origin-when-cross-origin'] + server: [GitHub.com] + status: [200 OK] + strict-transport-security: [max-age=31536000; includeSubdomains; preload] + vary: ['Accept, Authorization, Cookie, X-GitHub-OTP'] + x-content-type-options: [nosniff] + x-frame-options: [deny] + x-github-media-type: [github.v3; param=full; format=json] + x-github-request-id: ['8982:7DB6:A098CD2:147496D3:5B4BC9DA'] + x-ratelimit-limit: ['5000'] + x-ratelimit-remaining: ['4998'] + x-ratelimit-reset: ['1531697130'] + x-runtime-rack: ['0.094512'] + x-xss-protection: [1; mode=block] + status: {code: 200, message: OK} +version: 1 diff --git a/tests/test_cli.py b/tests/test_cli.py index 0f27778..2857c1c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,10 +2,28 @@ from __future__ import absolute_import, division, print_function +import pytest + from github_file.cli import cli +GITHUB_FILE = '''\ +[core] +owner = jacquerie +repo = github-file +description = Configure your GitHub repository from a file, + without having to click around in the UI. + +[features] +has_issues = true +has_wiki = false +''' + + +@pytest.mark.vcr() +def test_update_uses_the_provided_filename(runner, tmpdir): + config_fd = tmpdir.join('github-compose.yaml') + config_fd.write(GITHUB_FILE) -def test_update_uses_the_provided_filename(runner): - result = runner.invoke(cli, ['update', '-f', 'Githubfile']) + result = runner.invoke(cli, ['update', '-f', str(config_fd)]) assert 0 == result.exit_code