Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Convert to v3 of the Github API.
cligh now uses PyGithub instead of the python-github2 library.
See README.md for a list of dependencies.
  • Loading branch information
CMB committed Jul 25, 2012
1 parent 3fc05c1 commit 7f45255
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 76 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
*.pyc
7 changes: 5 additions & 2 deletions README.md
Expand Up @@ -21,8 +21,11 @@ install the argparse module.
Get it from [here](http://argparse.googlecode.com/).
Python version 2.7 includes argparse in its standard library.

cligh also requires the python-github2 package.
The homepage for python-github2 is [here](http://github.com/ask/python-github2).
cligh also requires the PyGithub package.
The homepage for PyGithub is [https://github.com/jacquev6/PyGithub](https://github.com/jacquev6/PyGithub).

The final dependency is PyXDG.
Get it from [http://freedesktop.org/Software/pyxdg](http://freedesktop.org/Software/pyxdg).

Once the dependencies are installed,
type `./setup.py` in the cligh source directory, in order to install
Expand Down
15 changes: 9 additions & 6 deletions bin/cligh
Expand Up @@ -3,29 +3,32 @@
# See the file LICENSE for copyright and license info.
import argparse

import github2.client
from github import Github

from cligh import config
from cligh.collaborators import make_collab_parser
from cligh.issues import make_issue_parser
from cligh.repos import make_repo_parser
from cligh.utils import get_username_and_token

# Option parsing.
def make_argument_parser():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='Subcommands')
config.make_configcmd_parser(subparsers)
make_collab_parser(subparsers)
make_issue_parser(subparsers)
make_repo_parser(subparsers)
return parser

def main():
"""The main function."""
username, token = get_username_and_token()
client = github2.client.Github(username, token)
parser = make_argument_parser()
args = parser.parse_args()
args.func(client, args)
if args.func == config.do_configcmd:
config.do_configcmd()
else:
config.read_config_file()
client = Github(config.get_token())
args.func(client, args)

if __name__ == '__main__':
main()
26 changes: 14 additions & 12 deletions cligh/collaborators.py
@@ -1,31 +1,33 @@
#!/usr/bin/python
# Commands for managing collaborators.

from cligh.utils import get_repository_name, remove_username
from . import utils

def add(client, args):
"""Add a collaborator to a repo."""
repository = get_repository_name(args.repository)
repository = remove_username(repository)
print client.repos.add_collaborator(repository, args.user)
repository = utils.get_working_repo(client, args.repository)
collaborator = utils.get_named_user(client, args.user)
repository.add_to_collaborators(collaborator)
print 'Collaborator added.'

def remove(client, args):
"""Remove a collaborator from a repo."""
repository = get_repository_name(args.repository)
repository = remove_username(repository)
print client.repos.remove_collaborator(repository, args.user)
repository = utils.get_working_repo(client, args.repository)
collaborator = utils.get_named_user(client, args.user)
repository.remove_from_collaborators(collaborator)
print 'Collaborator removed.'

def do_list(client, args):
"""List a repository's collaborators."""
repository = get_repository_name(args.repository)
collaborators = client.repos.list_collaborators(repository)
repository = utils.get_working_repo(client, args.repository)
collaborators = repository.get_collaborators()
if not collaborators:
print 'There are no collaborators for %s.' % repository
print 'There are no collaborators for %s.' % repository.full_name
else:
print 'The following people are collaborating on %s:' % \
repository
repository.full_name
for collaborator in collaborators:
print collaborator
print collaborator.login

def make_collab_parser(subparsers):
collab = subparsers.add_parser('collab', help='Manage collaborators.')
Expand Down
83 changes: 83 additions & 0 deletions cligh/config.py
@@ -0,0 +1,83 @@
import ConfigParser
import getpass
import os
from github import Github
from xdg import BaseDirectory
from . import utils

USERNAME = None
TOKEN = None

def get_username():
global USERNAME
return USERNAME

def get_token():
global TOKEN
return TOKEN

def get_config_dir():
"""Return the name of the directory containing the application's config file."""
config_dir = BaseDirectory.load_first_config('cligh')
if config_dir is None:
config_dir = BaseDirectory.save_config_path('cligh')
return config_dir

def get_config_filename():
"""Get the absolute pathname of the config file."""
config_dir = get_config_dir()
return os.path.join(config_dir, 'cligh.conf')

def read_config_file():
global USERNAME, TOKEN
config_parser = ConfigParser.ConfigParser()
config_filename = get_config_filename()
try:
with open(config_filename, 'r') as f:
config_parser.readfp(f)
except ConfigParser.Error as e:
utils.die("""The following error was encountered while attempting to parse the configuration file.
%s
This may indicate a mal-formed configuration file.
Recreate the file by invoking
cligh configure
""" % str(e))
except IOError as e:
utils.die("""The following error occurred while trying to open the configuration file.
%s.
If you have not already done so, create the configuration file using
cligh configure
at your shell prompt.
""" % str(e))

try:
USERNAME = config_parser.get('credentials', 'username')
TOKEN = config_parser.get('credentials', 'token')
except ConfigParser.Error as e:
utils.die("""The config file is missing one or more expected options.
You should probably recreate it using these two commands:
rm %s
cligh configure
""" % config_filename)

def do_configcmd():
"""Create an oauth token. Write the username and token to the config
file. Should be called the first time the application is executed."""
dummy_validator = lambda x : True
username = utils.read_user_input('Username', dummy_validator)
password = getpass.getpass('Password:')
client = Github(username, password)
user = client.get_user()
authorization = user.create_authorization(scopes=['user', 'repo', 'gists', 'delete_repo'], note='cligh', note_url='https://github.com/CMB/cligh')
config_parser = ConfigParser.ConfigParser()
config_parser.add_section('credentials')
config_parser.set('credentials', 'username', username)
config_parser.set('credentials', 'token', authorization.token)
os.umask(077) # Want permissions of 0600.
with open(get_config_filename(), 'w') as f:
config_parser.write(f)
print 'cligh configured and authorized for use with github.'

def make_configcmd_parser(subparsers):
configcmd = subparsers.add_parser('configure', help='Configure cligh.')
configcmd.set_defaults(func=do_configcmd)
101 changes: 69 additions & 32 deletions cligh/issues.py
@@ -1,9 +1,26 @@
#!/usr/bin/python
"""Commands for managing and querying issues."""

from cligh.utils import text_from_editor, get_repository_name
from github import GithubException
from cligh.utils import text_from_editor, get_working_repo, die

# Helper functions:
def get_working_issue(client, args):
"""Get an object corresponding to the issue
that the user wishes to manipulate. Issues are identified by
a repository name and issue number."""
issue = None
repository = get_working_repo(client, args.repository)
try:
issue_number = int(args.number)
except ValueError:
die("""%s is not a valid issue number.""")
try:
issue = repository.get_issue(issue_number)
except GithubException as e:
die('Unable to fetch issue number %d for this repository: %s' % (args.number, e.data['message']))
return issue

# A helper function.
def print_enclosed_text(text):
"""Print some text, enclosed by horizontal lines."""
print '-' * 80
Expand All @@ -12,75 +29,95 @@ def print_enclosed_text(text):
print

def print_comment(comment):
print 'Comment by %s on %s at %s' % (comment.user,
print 'Comment by %s on %s at %s' % (comment.user.login,
comment.created_at.date(),
comment.created_at.strftime('%H:%M:%S'))
print_enclosed_text(comment.body)

def do_open(client, args):
"""Create a new issue."""
repository = get_repository_name(args.repository)
repository = get_working_repo(client, args.repository)
print 'Please enter the long description for this issue.'
print 'Starting your text editor:'
desc_text = text_from_editor()
print client.issues.open(repository, args.title, desc_text)
repository.create_issue(args.title, body=desc_text)

def close(client, args):
"""Close an existing open issue."""
repository = get_repository_name(args.repository)
print client.issues.close(repository, args.number)
issue = get_working_issue(client, args)
issue.edit(state='closed')

def do_list(client, args):
"""Command to list the issues for a given repository."""
repository = get_repository_name(args.repository)
repository = get_working_repo(client, args.repository)
status = args.status or 'open'
issues = client.issues.list(repository, state=status)
issues = list(repository.get_issues(state=status))
if not issues:
print '%s has no %s issues' % (repository, status)
print '%s has no %s issues' % (repository.full_name, status)
else:
print '%s has the following %s issues' % (repository, status)
print '%s has the following %s issues' % (repository.full_name, status)
print 'Issue# - Title'
for issue in issues:
print '%s - %s' % (issue.number, issue.title)

def get(client, args):
repository = get_repository_name(args.repository)
try:
issue = client.issues.show(repository, args.number)
comments = client.issues.comments(repository, args.number)
except:
die(
"""Unable to retrieve the issue because of the following error:
%s: %s
""" % (sys.exc_info()[0], sys.exc_info()[1]))
issue = get_working_issue(client, args)
comments = issue.get_comments()
print 'Issue #%d: %s' % (issue.number, issue.title)
print 'Opened by %s on %s at %s' % (issue.user, issue.created_at.date(),
issue.created_at.strftime('%H:%M:%S'))
print 'Opened by %s on %s at %s' % (issue.user.login,
issue.created_at.date(), issue.created_at.strftime('%H:%M:%S'))
print 'Last updated on %s at %s' % (issue.updated_at.date(),
issue.updated_at.strftime('%H:%M:%S'))
if issue.closed_by and issue.closed_at:
print "Closed by %s on %s at %s" % (issue.closed_by.login,
issue.closed_at.date(), issue.closed_at.strftime('%H:%M:%S'))
if issue.labels:
print 'Labels:'
for label in issue.labels:
print '* %s' % label
print '* %s' % label.name
print 'Long description:'
print_enclosed_text(issue.body)
if comments:
for comment in comments:
print_comment(comment)
print 'Comments:'
print
for comment in comments:
print_comment(comment)

def comment(client, args):
repository = get_repository_name(args.repository)
issue = get_working_issue(client, args)
print 'Starting your text editor, so that you can compose your comment:'
comment_text = text_from_editor()
print client.issues.comment(repository, args.number, comment_text)
issue.create_comment(comment_text)

def addlabel(client, args):
repository = get_repository_name(args.repository)
print client.issues.add_label(repository, args.number, args.label)
issue = get_working_issue(client, args)
repository = get_working_repo(client, args.repository)
try:
label = repository.get_label(args.label)
except GithubException as e:
if e.status == 404:
die('''The label %s has not yet been added to this repository.
First, add it using:
cligh repo addlabel %s
''' % (args.label, args.label))
else:
die('''Unable to find the label %s in this repository.
Error message: %s
''' % (args.label, e.data['message']))

issue.add_to_labels(label)

def remlabel(client, args):
repository = get_repository_name(args.repository)
print client.issues.remove_label(repository, args.number, args.label)
issue = get_working_issue(client, args)
repository = get_working_repo(client, args.repository)
try:
label = repository.get_label(args.label)
except GithubException as e:
die('''Unable to find the label %s in this repository.
It cannot be removed from the issue at this time.
Error message: %s
''' % (args.label, e.data['message']))

issue.remove_from_labels(label)

def make_issue_parser(subparsers):
issue = subparsers.add_parser('issue', help='Manage and query issues.')
Expand Down

0 comments on commit 7f45255

Please sign in to comment.