Last active
December 19, 2015 09:58
-
-
Save tgfrerer/5936499 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env python | |
""" | |
Gist | |
---- | |
Gist is a command line tool for interacting with `gist.github.com`_. Usage | |
information is available through the command line interface help. | |
.. _gist.github.com: <https://gist.github.com> | |
.. important:: | |
On your first usage the script will use basic auth to authorize an oauth | |
token. Once authorized you will not be asked for your username or password | |
again. Your password and username are never stored and are only sent to | |
Github.com over an HTTPS connection. | |
Here a few examples of useage:: | |
$ python gist.py create gist.py # Creates a new gist named gist.py | |
.. TODO: | |
* Make `create` and `edit` work with multiple files | |
* Add support for fork` | |
* Add documentation | |
""" | |
from argparse import ArgumentParser, FileType | |
import base64 | |
from contextlib import closing | |
import getpass | |
from httplib import HTTPSConnection | |
import json | |
import os | |
import sys | |
from urllib import urlencode | |
HOST = 'api.github.com' | |
# `copy_url` from lodgeit.py | |
# :license: 3-Clause BSD | |
# :authors: 2007-2008 Georg Brandl <georg@python.org>, | |
# 2006 Armin Ronacher <armin.ronacher@active-4.com>, | |
# 2006 Matt Good <matt@matt-good.net>, | |
# 2005 Raphael Slinckx <raphael@slinckx.net> | |
def copy_url(url): | |
"""Copy the url into the clipboard.""" | |
# try windows first | |
try: | |
import win32clipboard | |
except ImportError: | |
# then give pbcopy a try. do that before gtk because | |
# gtk might be installed on os x but nobody is interested | |
# in the X11 clipboard there. | |
from subprocess import Popen, PIPE | |
for prog in 'pbcopy', 'xclip': | |
try: | |
client = Popen([prog], stdin=PIPE) | |
except OSError: | |
continue | |
else: | |
client.stdin.write(url) | |
client.stdin.close() | |
client.wait() | |
break | |
else: | |
try: | |
import pygtk | |
pygtk.require('2.0') | |
import gtk | |
import gobject | |
except ImportError: | |
return | |
gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD).set_text(url) | |
gobject.idle_add(gtk.main_quit) | |
gtk.main() | |
else: | |
win32clipboard.OpenClipboard() | |
win32clipboard.EmptyClipboard() | |
win32clipboard.SetClipboardText(url) | |
win32clipboard.CloseClipboard() | |
def authorize(user, password): | |
with closing(HTTPSConnection(HOST, timeout=10)) as conn: | |
data = { | |
'scopes': ['gist'], | |
} | |
encoded_auth = base64.b64encode('%s:%s' % (user, password)) | |
conn.request('POST', '/authorizations', json.dumps(data), | |
{'Authorization': 'Basic %s' % encoded_auth, | |
'Content-Type': 'application/json', | |
'User-Agent' : 'pygist'}) | |
resp = conn.getresponse() | |
return json.loads(resp.read()) | |
def create(token, filename, content, message, public): | |
with closing(HTTPSConnection(HOST, timeout=10)) as conn: | |
data = { | |
'public': public, | |
'desciption': message, | |
'files': { | |
filename: { | |
"content": content | |
} | |
}, | |
} | |
path = '/gists' | |
conn.request('POST', path, json.dumps(data), | |
{ | |
'Content-Type': 'application/json', | |
'Authorization': 'bearer %s' % token, | |
'User-Agent' : 'pygist', | |
} | |
) | |
resp = conn.getresponse() | |
if resp.status >= 200 and resp.status < 300: | |
resp_data = json.loads(resp.read()) | |
print resp_data['html_url'] | |
copy_url(resp_data['html_url']) | |
else: | |
print '%s %s' % (resp.status, resp.read()) | |
def create_controller(token, namespace): | |
paste = namespace.paste | |
if not paste and not os.isatty(sys.stdin.fileno()): | |
paste = sys.stdin | |
if paste: | |
name = namespace.name or os.path.basename(paste.name) | |
create(token, name, paste.read(), | |
namespace.message, namespace.public) | |
def delete_controller(token, namespace): | |
for id_ in namespace.ids: | |
delete(token, id_, namespace.verbose) | |
def delete(token, id_, verbose): | |
path = '/gists/%s' % id_ | |
with closing(HTTPSConnection(HOST, timeout=10)) as conn: | |
conn.request('DELETE', path, headers={ | |
'Authorization': 'bearer %s' % token, | |
'User-Agent' : 'pygist', | |
} | |
) | |
resp = conn.getresponse() | |
if resp.status == 204: | |
print 'Deleted %s' % id_ | |
def edit_controller(token, namespace): | |
paste = namespace.paste | |
if not paste and not os.isatty(sys.stdin.fileno()): | |
paste = sys.stdin | |
filename = namespace.name or os.path.basename(paste.name) | |
path = '/gists/%s' % ( | |
namespace.id) | |
with closing(HTTPSConnection(HOST, timeout=10)) as conn: | |
data = { | |
'files': { | |
filename: { | |
"content": paste.read() | |
} | |
}, | |
} | |
if namespace.message: | |
data.update({'description': namespace.message}) | |
conn.request('PATCH', path, json.dumps(data), | |
{ | |
'Content-Type': 'application/json', | |
'Authorization': 'bearer %s' % token, | |
'User-Agent' : 'pygist', | |
}) | |
resp = conn.getresponse() | |
if resp.status >= 200 and resp.status < 300: | |
resp_data = json.loads(resp.read()) | |
print resp_data['html_url'] | |
copy_url(resp_data['html_url']) | |
else: | |
print '%s %s' % (resp.status, resp.read()) | |
def list_controller(token, namespace): | |
if namespace.all: | |
path = '/gists/public' | |
elif namespace.user: | |
path = '/users/%s/gists' % namespace.user | |
elif namespace.starred: | |
path = '/gists/starred' | |
else: | |
path = '/gists' | |
with closing(HTTPSConnection(HOST, timeout=10)) as conn: | |
conn.request('GET', path, headers={ | |
'Authorization': 'bearer %s' % token, | |
'User-Agent' : 'pygist', | |
}) | |
resp = conn.getresponse() | |
if resp.status == 200: | |
data = json.loads(resp.read()) | |
# Order by `created_date` descending | |
order_cmp = lambda x, y: x['created_at'] > y['created_at'] | |
for _gist in sorted(data, cmp=order_cmp): | |
if namespace.only_public and _gist['public'] == False: | |
continue | |
if namespace.only_private and _gist['public'] == True: | |
continue | |
if namespace.verbose: | |
print "%s:\n description: %s\n files: %s" % ( | |
_gist['html_url'], _gist['description'], | |
' '.join(_gist['files'].keys())) | |
else: | |
print _gist['html_url'] | |
else: | |
print ' '.join([str(resp.status), resp.read()]) | |
def star_controller(token, namespace): | |
for id_ in namespace.ids: | |
star(token, id_, namespace.verbose) | |
def star(token, id_, verbose): | |
path = '/gists/%s/star' % id_ | |
with closing(HTTPSConnection(HOST, timeout=10)) as conn: | |
conn.request('PUT', path, headers={ | |
'Content-Length': '0', | |
'Authorization': 'bearer %s' % token, | |
}) | |
resp = conn.getresponse() | |
if resp.status == 204: | |
print 'Starred %s' % id_ | |
def unstar_controller(token, namespace): | |
for id_ in namespace.ids: | |
unstar(token, id_, namespace.verbose) | |
def unstar(token, id_, verbose): | |
path = '/gists/%s/star' % id_ | |
with closing(HTTPSConnection(HOST, timeout=10)) as conn: | |
conn.request('DELETE', path, headers={ | |
'Authorization': 'bearer %s' % token, | |
'User-Agent' : 'pygist', | |
}) | |
resp = conn.getresponse() | |
if resp.status == 204: | |
print 'Unstarred %s' % id_ | |
def view_controller(token, namespace): | |
for id_ in namespace.ids: | |
view(token, id_, namespace.verbose) | |
def view(token, gist_id, verbose): | |
path = '/gists/%s' % gist_id | |
with closing(HTTPSConnection(HOST, timeout=10)) as conn: | |
conn.request('GET', path, headers={ | |
'Authorization': 'bearer %s' % token, | |
'User-Agent' : 'pygist', | |
}) | |
resp = conn.getresponse() | |
if resp.status == 200: | |
data = json.loads(resp.read()) | |
for file_ in data['files'].values(): | |
if verbose: | |
print '-' * 10 | |
print 'name:%s \n raw_url: %s \n size: %s' % ( | |
file_['filename'], file_['raw_url'], file_['size']) | |
print '-' * 10 | |
print file_['content'] | |
_cmd_controllers = { | |
'create': create_controller, | |
'delete': delete_controller, | |
'edit': edit_controller, | |
'list': list_controller, | |
'star': star_controller, | |
'unstar': unstar_controller, | |
'view': view_controller, | |
} | |
def main(): | |
parser = ArgumentParser() | |
parser.add_argument('-c', '--config', | |
help='Configuration to use, defaults to ~/.gist', | |
default=os.path.join('~', '.gist')) | |
parser.add_argument('-v', '--verbose', | |
help='Turn on additional output', action='store_true') | |
parser.add_argument('--login', help='Github login to use', | |
default=getpass.getuser()) | |
subparsers = parser.add_subparsers(dest="cmd") | |
create_parser = subparsers.add_parser('create', help='Create a new gist') | |
create_parser.add_argument( | |
'-n', '--name', | |
help='Give an explicit filename. Most useful for stdin.', | |
default=None) | |
create_parser.add_argument( | |
'-m', '--message', help='A message describing this gist', default='') | |
create_parser.add_argument( | |
'--public', help='Set the default visibility for this paste.', | |
default=True) | |
create_parser.add_argument( | |
'paste', nargs='?', help='Accepts a filename or - for stdin', | |
type=FileType('r'), default=None) | |
delete_parser = subparsers.add_parser( | |
'delete', help='View a specific gist') | |
delete_parser.add_argument('ids', nargs='+') | |
edit_parser = subparsers.add_parser( | |
'edit', help='View a specific gist') | |
edit_parser.add_argument('--id', help='Gist id') | |
edit_parser.add_argument('paste', nargs='?', | |
help='Accepts a filename or - for stdin', | |
type=FileType('r')) | |
edit_parser.add_argument( | |
'-m', '--message', help='A message describing this gist', default='') | |
edit_parser.add_argument( | |
'-n', '--name', | |
help='Give an explicit filename. Most useful for stdin.', | |
default=None) | |
list_parser = subparsers.add_parser( | |
'list', help='List gists. Defaults to showing all your gists') | |
list_group = list_parser.add_mutually_exclusive_group() | |
list_group.add_argument( | |
'-u', '--user', help='A users public gists', default=None) | |
list_group.add_argument( | |
'-a', '--all', help='All public gists', action='store_true') | |
list_group.add_argument( | |
'--only-private', help='Only your private gists', action='store_true') | |
list_group.add_argument( | |
'--only-public', help='Only your public gists', action='store_true') | |
list_group.add_argument( | |
'--starred', help='Your starred gists', action='store_true') | |
star_parser = subparsers.add_parser( | |
'star', help='View a specific gist') | |
star_parser.add_argument('ids', nargs='+') | |
unstar_parser = subparsers.add_parser( | |
'unstar', help='View a specific gist') | |
unstar_parser.add_argument('ids', nargs='+') | |
view_parser = subparsers.add_parser( | |
'view', help='View a specific gist') | |
view_parser.add_argument('ids', nargs='+') | |
namespace = parser.parse_args() | |
auth = {} | |
info_path = os.path.expanduser(namespace.config) | |
if os.path.exists(info_path): | |
with open(info_path) as fp: | |
auth = json.load(fp) | |
else: | |
auth = authorize(namespace.login, getpass.getpass('Password: ')) | |
with open(info_path, 'w+') as fp: | |
json.dump(auth, fp) | |
fn = _cmd_controllers.get(namespace.cmd) | |
if fn: | |
fn(auth['token'], namespace) | |
else: | |
parser.print_help() | |
sys.exit(-1) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment