├── .gitignore ├── .travis.yml ├── AUTHORS.txt ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs └── index.rst ├── octogit ├── __init__.py ├── __main__.py ├── cli.py ├── config.py └── core.py ├── requirements.txt ├── setup.py ├── tests ├── cli.py ├── config.py └── core.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.db 3 | *.swp 4 | octogit.egg-info 5 | .DS_Store 6 | build* 7 | dist* 8 | .tox 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.6 4 | - 2.7 5 | script: make check 6 | install: 7 | - pip install nose 8 | - pip install -r requirements.txt 9 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Mahdi Yusuf (@myusuf3) -- Lead Developer 2 | TR Jordan (@trjordan) 3 | Martin (@posativ) 4 | Mike Grouchy (@mgrouchy) 5 | Ben Dickson (@_dbr (dbr on Github)) 6 | Richo Healey (@rich0H (richo on GitHub)) 7 | Cesar Frias (@cesarFrias) 8 | Satshabad Khalsa (@satshabad) 9 | Zack Hein (@zhein) 10 | Mike DeHart (mikedehart on GitHub) 11 | Nils Diefenbach (@nlsdfnbch on GitHub) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Mahdi Yusuf 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include setup.py 2 | include tests/* 3 | recursive-include docs *.rst *.py *.html *.css *.js *.png *.txt 4 | include tox.ini 5 | include README.rst 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | TESTS=$(shell find tests/ -name "*.py") 3 | 4 | 5 | check: 6 | nosetests ${TESTS} 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Octogit 3 | ======== 4 | 5 | .. image:: https://github.com/myusuf3/octogit/raw/gh-pages/assets/img/readme_image.png 6 | :align: center 7 | 8 | A **free** and **open source** interface to github from the command line. Avoid the usual copy and paste when creating repositories, keep up to date on issues, and much more. 9 | 10 | --Everybody loves more tentacles. 11 | 12 | 13 | Installation 14 | ============ 15 | 16 | Installing using pip. :: 17 | 18 | pip install octogit 19 | 20 | 21 | Using Octogit 22 | ============== 23 | 24 | Available commands. :: 25 | 26 | octogit login 27 | # allows you to store your github authentication data 28 | 29 | octogit create 'description' 30 | # lets you create the repository both locally and on github 31 | 32 | octogit issues 33 | # lets you see all the related issues in the current repository 34 | 35 | octogit issues 36 | # lets you see a specific issue with summary 37 | 38 | octogit issues close 39 | # lets you close an issue 40 | 41 | 42 | Contribute 43 | ========== 44 | If you would like to contribute simply fork this project and add yourself to the AUTHORS.txt along with some changes hopefully and submit a pull request. 45 | 46 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myusuf3/octogit/4eebd844e44c4b8da3f49bac0e70fc12375c2f94/docs/index.rst -------------------------------------------------------------------------------- /octogit/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = (0, 4, 0) 2 | -------------------------------------------------------------------------------- /octogit/__main__.py: -------------------------------------------------------------------------------- 1 | """Run octogit from CLI. 2 | 3 | Creates a parser object with several subcommands, which implement octogit's 4 | functionality. 5 | 6 | The parser tree looks as follows:: 7 | 8 | octogit 9 | ├── login {username} {password} 10 | │ 11 | ├── create {repository} 12 | │ │ 13 | │ ├── -d | --description 14 | │ └── -o | --organisation 15 | │ 16 | └── issues 17 | │ 18 | ├── list [-a | --assigned] 19 | ├── close {issue-number} 20 | ├── view {issue-number} 21 | └── create {title} 22 | │ 23 | └── -d | --description 24 | """ 25 | import os 26 | import argparse 27 | from config import CONFIG_FILE, create_config, login 28 | from cli import version 29 | from core import ( 30 | get_issues, 31 | get_single_issue, 32 | create_repository, 33 | close_issue, 34 | view_issue, 35 | create_issue, 36 | find_github_remote, 37 | get_username_and_repo, 38 | ) 39 | 40 | base_parser = argparse.ArgumentParser( 41 | prog="octogit", epilog="Version {} by Mahdi Yusuf @myusuf".format(version()) 42 | ) 43 | 44 | 45 | sub_parsers = base_parser.add_subparsers() 46 | 47 | # Build Login parser 48 | login_parser = sub_parsers.add_parser("login") 49 | login_parser.add_argument("username", action='store', help="Your Github username.") 50 | login_parser.add_argument("password", action='store', help="Your Github password.") 51 | 52 | # Build create parser for repository creation. 53 | create_parser = sub_parsers.add_parser("create") 54 | create_parser.add_argument("repository", action='store') 55 | create_parser.add_argument("--description", nargs=1, action='store', default="") 56 | create_parser.add_argument("--organisation", nargs=1, action='store', default="") 57 | 58 | # Build issues parser and subparsers 59 | issues_parser = sub_parsers.add_parser("issues") 60 | issues_subparsers = issues_parser.add_subparsers() 61 | 62 | # Enale Listing issues (one specific one or all available) 63 | issues_list_parser = issues_subparsers.add_parser("list") 64 | issues_list_parser.add_argument("-a", "--assigned", action="store_true", default=False) 65 | 66 | issues_close_parser = issues_subparsers.add_parser("close") 67 | issues_close_parser.add_argument('issue') 68 | 69 | issues_view_parser = issues_subparsers.add_parser("view") 70 | issues_view_parser.add_argument('issue') 71 | 72 | issues_create_parser = issues_subparsers.add_parser("create") 73 | issues_create_parser.add_argument('title', action='store') 74 | issues_create_parser.add_argument('--description', nargs=1, action='store') 75 | 76 | options = base_parser.parse_args() 77 | 78 | 79 | if not os.path.exists(CONFIG_FILE): 80 | create_config() 81 | 82 | if options.create: 83 | project_name = options.repository 84 | description = options.descriptions 85 | organization = options.organization 86 | create_repository(project_name, description, organization=organization) 87 | elif options.issues: 88 | url = find_github_remote() 89 | username, url = get_username_and_repo(url) 90 | if options.create: 91 | title = options.title 92 | description = options.description 93 | create_issue(username, url, title, description) 94 | elif options.list: 95 | get_issues(username, url, options.assigned) 96 | elif options.view: 97 | view_issue(username, url, options.issue) 98 | elif options.close: 99 | close_issue(username, url, options.issue) 100 | else: 101 | get_issues(username, url, False) 102 | elif options.login: 103 | username = options.username 104 | password = options.password 105 | login(username, password) 106 | -------------------------------------------------------------------------------- /octogit/cli.py: -------------------------------------------------------------------------------- 1 | """ 2 | octogit 3 | 4 | this file contains all the helper cli commands for octogit 5 | 6 | """ 7 | import re 8 | 9 | import requests 10 | 11 | 12 | GIT_REPO_ENDPOINT = 'https://api.github.com/repos/%s/%s' 13 | 14 | 15 | def version(): 16 | from . import __version__ 17 | return ".".join(str(x) for x in __version__) 18 | 19 | 20 | def get_parent_repository(username_repo): 21 | username, repo = username_repo 22 | url = GIT_REPO_ENDPOINT % (username, repo) 23 | response = requests.get(url) 24 | data = response.json() 25 | try: 26 | parent = data['parent']['full_name'] 27 | username_repo = parent.split('/') 28 | except KeyError: 29 | pass 30 | return username_repo 31 | 32 | 33 | def get_username_and_repo(url): 34 | # matching origin of this type 35 | # http://www.github.com/myusuf3/delorean 36 | m = re.match("^.+?github.com/([a-zA-Z0-9_-]*)/([a-zA-Z0-9_-]*)\/?$", url) 37 | if m: 38 | return m.groups() 39 | else: 40 | # matching origin of this type 41 | # git@github.com:[/]myusuf3/delorean.git 42 | username_repo = url.split(':')[1].replace('.git', '').split('/') 43 | # Handle potential leading slash after : 44 | if username_repo[0] == '': 45 | username_repo = username_repo[1:] 46 | if len(username_repo) == 2: 47 | info = username_repo 48 | else: 49 | # matching url of this type 50 | # git://github.com/myusuf3/delorean.git 51 | username_repo = url.split('/')[3:] 52 | username_repo[1] = username_repo[1].replace('.git', '') 53 | info = username_repo 54 | parent_repo = get_parent_repository(info) 55 | return parent_repo 56 | -------------------------------------------------------------------------------- /octogit/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import requests 5 | 6 | from clint.textui import colored, puts 7 | from six.moves import configparser 8 | 9 | 10 | try: 11 | import json 12 | except ImportError: 13 | import simplejson as json # NOQA 14 | 15 | CONFIG_FILE = os.path.expanduser('~/.config/octogit/config.ini') 16 | # ran the first time login in run 17 | # config = configparser.ConfigParser() 18 | 19 | 20 | def get_parser(): 21 | try: 22 | config = configparser.ConfigParser() 23 | config.read(CONFIG_FILE) 24 | except Exception as e: 25 | puts(colored.red( 26 | "ERROR: Attempting to get config parser failed: {}".format(e) 27 | ) 28 | ) 29 | config = None 30 | return config 31 | 32 | 33 | def commit_changes(config): 34 | """ 35 | Write changes to the config file. 36 | """ 37 | with open(CONFIG_FILE, 'w') as configfile: 38 | config.write(configfile) 39 | 40 | 41 | def create_config(): 42 | """Create a configuration file for octogit.""" 43 | if os.path.isfile(CONFIG_FILE): 44 | return 45 | # Make sure the path exists, and if it doesn't, create it. 46 | if not os.path.exists(os.path.dirname(CONFIG_FILE)): 47 | os.makedirs(os.path.dirname(CONFIG_FILE)) 48 | # touch the config file. 49 | open(CONFIG_FILE, 'a').close() 50 | config = get_parser() 51 | config.add_section('octogit') 52 | config.set('octogit', 'username', '') 53 | config.set('octogit', 'token', '') 54 | commit_changes(config) 55 | 56 | 57 | def get_token(): 58 | config = get_parser() 59 | # Catch edgecase where user hasn't migrated to tokens 60 | try: 61 | return config.get('octogit', 'token') 62 | except configparser.NoOptionError: 63 | if get_username() == "": 64 | raise 65 | puts(colored.green( 66 | "We're just migrating your account from plaintext passwords to " 67 | "OAuth tokens" 68 | )) 69 | login(get_username(), config.get('octogit', 'password')) 70 | config.remove_option('octogit', 'password') 71 | puts(colored.green("Pretty spiffy huh?")) 72 | return config.get('octogit', 'token') 73 | 74 | 75 | def get_username(): 76 | config = get_parser() 77 | return config.get('octogit', 'username') 78 | 79 | 80 | def get_headers(headers=()): 81 | defaults = {"Authorization": "token %s" % get_token()} 82 | defaults.update(headers) 83 | return defaults 84 | 85 | 86 | def have_credentials(): 87 | return get_username() != '' and get_token() != '' 88 | 89 | 90 | def set_token(token): 91 | """ 92 | Given a config set the token attribute 93 | in the Octogit section. 94 | """ 95 | config = get_parser() 96 | config.set('octogit', 'token', token) 97 | commit_changes(config) 98 | 99 | 100 | def set_username(username): 101 | """ 102 | Given a config set the username attribute 103 | in the Octogit section. 104 | """ 105 | config = get_parser() 106 | config.set('octogit', 'username', username) 107 | commit_changes(config) 108 | 109 | 110 | def login(username, password): 111 | payload = { 112 | "note": "octogit", 113 | "note_url": "https://github.com/myusuf3/octogit", 114 | "scopes": ["repo"] 115 | } 116 | response = requests.post( 117 | 'https://api.github.com/authorizations', 118 | auth=(username, password), 119 | json=payload, 120 | ) 121 | if response.status_code == 201: 122 | puts(colored.green('You have successfully been authenticated with Github')) 123 | data = json.loads(response.content) 124 | token = data["token"] 125 | set_username(username) 126 | set_token(token) 127 | elif response.status_code == 422: 128 | puts(colored.red('An access token already exists! Exiting...')) 129 | sys.exit() 130 | else: 131 | msg = '{}. {}'.format( 132 | colored.blue('octogit'), 133 | colored.red('Do you even have a Github account? Bad Credentials') 134 | ) 135 | puts(msg) 136 | sys.exit(3) 137 | 138 | -------------------------------------------------------------------------------- /octogit/core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | octogit 4 | 5 | This file contains stuff for github api 6 | """ 7 | 8 | import io 9 | import os 10 | import re 11 | import subprocess 12 | import sys 13 | import webbrowser 14 | 15 | import requests 16 | 17 | from clint.textui import colored, puts, columns 18 | from six import print_ 19 | from six.moves import xrange 20 | 21 | from octogit.config import get_username, get_headers, have_credentials 22 | 23 | try: 24 | import json 25 | except ImportError: 26 | import simplejson as json # NOQA 27 | 28 | 29 | ISSUES_ENDPOINT = 'https://api.github.com/repos/%s/%s/issues?page=%s' 30 | CREATE_ISSUE_ENDPOINT = 'https://api.github.com/repos/%s/%s/issues' 31 | SINGLE_ISSUE_ENDPOINT = 'https://api.github.com/repos/%s/%s/issues/%s' 32 | ISSUES_PAGE = 'https://github.com/%s/%s/issues' 33 | SINGLE_ISSUE_PAGE = 'https://github.com/%s/%s/issues/%s' 34 | 35 | UPDATE_ISSUE = 'https://api.github.com/repos/%s/%s/issues/%s' 36 | 37 | 38 | def valid_credentials(): 39 | response = requests.get('https://api.github.com', headers=get_headers()) 40 | if response.status_code == 200: 41 | return True 42 | else: 43 | return False 44 | 45 | 46 | def push_to_master(): 47 | cmd = ['git', 'push', '-u', 'origin', 'master'] 48 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 49 | proc.communicate() 50 | 51 | 52 | def create_octogit_readme(): 53 | with io.open('README.rst', 'w') as fp: 54 | fp.write(u"""======== 55 | Octogit 56 | ======== 57 | 58 | This repository has been created with Octogit. 59 | 60 | .. image:: http://myusuf3.github.com/octogit/assets/img/readme_image.png 61 | 62 | Author 63 | ====== 64 | Mahdi Yusuf (@myusuf3) 65 | """) 66 | 67 | 68 | def git_init(repo_name): 69 | cmd = ["git", "init", repo_name] 70 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 71 | proc.communicate() 72 | 73 | 74 | def git_add_remote(username, repo_name): 75 | url = "git@github.com:%s/%s.git" % (username, repo_name) 76 | cmd = ["git", "remote", "add", "origin", url] 77 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 78 | proc.communicate() 79 | 80 | 81 | def git_initial_commit(): 82 | cmd = [ 83 | "git", "commit", "-am", 84 | "this repository now with more tentacles care of octogit" 85 | ] 86 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 87 | proc.communicate() 88 | 89 | 90 | def git_add(): 91 | cmd = ["git", "add", "README.rst"] 92 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 93 | proc.communicate() 94 | 95 | 96 | def local_already(repo_name): 97 | # mkdir repo_name 98 | if os.path.exists('/'.join([os.getcwd(), repo_name])): 99 | puts('{0}. {1}'.format(colored.blue('octogit'), 100 | colored.red('the repository already exists locally.'))) 101 | return True 102 | else: 103 | return False 104 | 105 | 106 | def create_local_repo(username, repo_name): 107 | # mkdir repo_name 108 | if os.path.exists('/'.join([os.getcwd(), repo_name])): 109 | puts('{0}. {1}'.format(colored.blue('octogit'), 110 | colored.red('the repository already exists locally.'))) 111 | else: 112 | os.makedirs('/'.join([os.getcwd(), repo_name])) 113 | # cd repo_name 114 | os.chdir('/'.join([os.getcwd(), repo_name])) 115 | # git init 116 | git_init(os.getcwd()) 117 | # create readme 118 | create_octogit_readme() 119 | # add readme 120 | git_add() 121 | # initial commit 122 | git_initial_commit() 123 | # add remote 124 | git_add_remote(username, repo_name) 125 | # push to master 126 | push_to_master() 127 | puts('{0}. {1}'.format(colored.blue('octogit'), 128 | colored.green('this is your moment of glory; Be a hero.'))) 129 | 130 | 131 | def close_issue(user, repo, number): 132 | if not have_credentials(): 133 | puts('{0}. {1}'.format(colored.blue('octogit'), 134 | colored.red('in order to create a repository, you need to login.'))) 135 | sys.exit(-1) 136 | update_issue = UPDATE_ISSUE % (user, repo, number) 137 | post_dict = {'state': 'close'} 138 | response = requests.post( 139 | update_issue, 140 | headers=get_headers(), 141 | data=json.dumps(post_dict) 142 | ) 143 | 144 | if response.status_code == 200: 145 | puts('{0}.'.format(colored.red('closed'))) 146 | else: 147 | puts('{0}. {1}'.format( 148 | colored.blue('octogit'), 149 | colored.red("You either aren't allowed to close repository " 150 | "or you need to login in silly.") 151 | ) 152 | ) 153 | sys.exit(-1) 154 | 155 | 156 | def view_issue(user, repo, number): 157 | """ 158 | Displays the specified issue in a browser 159 | """ 160 | github_view_url = SINGLE_ISSUE_PAGE % (user, repo, number) 161 | webbrowser.open(github_view_url) 162 | 163 | 164 | def create_repository(project_name, description, organization=None): 165 | if not have_credentials(): 166 | puts('{0}. {1}'.format(colored.blue('octogit'), 167 | colored.red('in order to create a repository, you need to login.'))) 168 | sys.exit(1) 169 | 170 | if local_already(project_name): 171 | sys.exit(1) 172 | post_dict = { 173 | 'name': project_name, 174 | 'description': description, 175 | 'homepage': '', 176 | 'private': False, 177 | 'has_issues': True, 178 | 'has_wiki': True, 179 | 'has_downloads': True 180 | } 181 | 182 | if organization: 183 | post_url = 'https://api.github.com/orgs/{0}/repos'.format(organization) 184 | else: 185 | post_url = 'https://api.github.com/user/repos' 186 | 187 | response = requests.post(post_url, headers=get_headers(), data=json.dumps(post_dict)) 188 | if response.status_code == 201: 189 | if organization: 190 | create_local_repo(organization, project_name) 191 | else: 192 | create_local_repo(get_username(), project_name) 193 | else: 194 | # Something went wrong 195 | post_response = json.loads(response.content) 196 | errors = post_response.get('errors') 197 | if errors and errors[0]['message'] == 'name already exists on this account': 198 | puts('{0}. {1}'.format( 199 | colored.blue('octogit'), 200 | colored.red('repository named this already exists on github') 201 | ) 202 | ) 203 | else: 204 | puts('{0}. {1}'.format( 205 | colored.blue('octogit'), 206 | colored.red('something went wrong. perhaps you need to login?') 207 | ) 208 | ) 209 | sys.exit(-1) 210 | 211 | 212 | def find_github_remote(): 213 | cmd = ["git", "remote", "-v"] 214 | 215 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 216 | stdout, sterr = proc.communicate() 217 | 218 | if not stdout: 219 | puts('{0}. {1}'.format(colored.blue('octogit'), 220 | colored.red('You need to be inside a valid git repository.'))) 221 | sys.exit(0) 222 | 223 | remotes = stdout.strip().split('\n') 224 | for line in remotes: 225 | name, url, _ = line.split() 226 | if 'github.com' in url and name == 'origin': 227 | return url 228 | else: 229 | puts(colored.red('This repository has no Github remotes')) 230 | sys.exit(0) 231 | 232 | 233 | def description_clean(string): 234 | new_string = '' 235 | for line in string.split('\n'): 236 | if line.strip(): 237 | new_string += line + '\n' 238 | return new_string 239 | 240 | 241 | def get_single_issue(user, repo, number): 242 | url = SINGLE_ISSUE_ENDPOINT % (user, repo, number) 243 | github_single_url = SINGLE_ISSUE_PAGE % (user, repo, number) 244 | puts('link. {0} \n'.format(colored.green(github_single_url))) 245 | if valid_credentials(): 246 | connect = requests.get(url, headers=get_headers()) 247 | else: 248 | connect = requests.get(url) 249 | 250 | issue = json.loads(connect.content) 251 | width = [[colored.yellow('#'+str(issue['number'])), 5],] 252 | width.append([colored.red('('+ issue['user']['login']+')'), 15]) 253 | puts(columns(*width)) 254 | description = description_clean(issue['body'].encode('utf-8')) 255 | puts(description) 256 | 257 | 258 | def get_issues(user, repo, assigned=None): 259 | github_issues_url = 'https://api.github.com/repos/%s/%s/issues' % (user, repo) 260 | 261 | params = None 262 | if assigned: 263 | params = {'assignee': user} 264 | 265 | link = requests.head(github_issues_url).headers.get('Link', '=1>; rel="last"') 266 | last = lambda url: int(re.compile('=(\d+)>; rel="last"$').search(url).group(1)) + 1 267 | 268 | for pagenum in xrange(1, last(link)): 269 | connect = requests.get(github_issues_url + '?page=%s' % pagenum, params=params) 270 | 271 | try: 272 | data = json.loads(connect.content) 273 | except ValueError: 274 | raise ValueError(connect.content) 275 | 276 | if not data: 277 | puts('{0}. {1}'.format(colored.blue('octogit'), 278 | colored.cyan('Looks like you are perfect welcome to the club.'))) 279 | break 280 | 281 | elif 'message' in data: 282 | puts('{0}. {1}'.format(colored.blue('octogit'), 283 | colored.red(data['message']))) 284 | sys.exit(1) 285 | 286 | for issue in data: 287 | # skip pull requests 288 | try: 289 | if issue['pull_request']['html_url']: 290 | continue 291 | width = [[colored.yellow('#'+str(issue['number'])), 4],] 292 | if isinstance(issue['title'], unicode): 293 | issue['title'] = issue['title'].encode('utf-8') 294 | width.append([issue['title'], 80]) 295 | width.append([colored.red('('+ issue['user']['login']+')'), None]) 296 | print_(columns(*width)) 297 | except IndexError as err: 298 | puts('{0}.Error: {1} triggered -- {2}'.format(colored.blue('octogit'), 299 | colored.red('Keyerror'), 300 | colored.red(err))) 301 | 302 | 303 | def create_issue(user, repo, issue_name, description): 304 | if not have_credentials(): 305 | puts('{0}. {1}'.format(colored.blue('octogit'), 306 | colored.red('in order to create an issue, you need to login.'))) 307 | sys.exit(1) 308 | 309 | post_url = CREATE_ISSUE_ENDPOINT % (user, repo) 310 | post_dict = {'title': issue_name, 'body': description} 311 | 312 | response = requests.post(post_url, headers=get_headers(), data=json.dumps(post_dict)) 313 | if response.status_code == 201: 314 | puts('{0}. {1}'.format(colored.blue('octogit'), 315 | colored.red('New issue created!'))) 316 | else: 317 | puts('{0}. {1}'.format(colored.blue('octogit'), 318 | colored.red('something went wrong. perhaps you need to login?'))) 319 | sys.exit(-1) 320 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=1.0 2 | simplejson==2.3.2 3 | clint2 4 | docopt 5 | nose 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | ======== 5 | Octogit 6 | ======== 7 | 8 | Do you hate this screen? Do you hate switching screens to see issues? Do you love the 9 | terminal? Then you will love this project. 10 | 11 | During the development of this plugin Github smartened up and introduced a new way to 12 | create repositories. Hopefully people who like to stay in the terminal will enjoy this 13 | little cli app. 14 | 15 | .. image:: https://github.com/myusuf3/octogit/raw/gh-pages/assets/img/readme_image.png 16 | 17 | 18 | Installation 19 | ============ 20 | 21 | `pip install octogit` 22 | 23 | 24 | How to Octogit 25 | ============== 26 | 27 | Go to http://myusuf3.github.com/octogit 28 | 29 | 30 | Contribute 31 | ========== 32 | If you would like to contribute simply fork this project and add yourself to the 33 | AUTHORS.txt along with some changes hopefully and submit a pull request. 34 | 35 | """ 36 | 37 | import os 38 | import sys 39 | 40 | try: 41 | from setuptools import setup 42 | except ImportError: 43 | from distutils.core import setup 44 | 45 | 46 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) 47 | 48 | from octogit import __version__ 49 | 50 | def publish(): 51 | os.system("python setup.py sdist upload") 52 | 53 | if sys.argv[-1] == "publish": 54 | publish() 55 | sys.exit() 56 | 57 | dependencies = ['clint2','requests>=1.0', 'docopt', 'six'] 58 | 59 | setup( 60 | name='octogit', 61 | version=".".join(str(x) for x in __version__), 62 | description='giving git tentacles to work better with github', 63 | url='https://github.com/myusuf3/octogit', 64 | author='Mahdi Yusuf', 65 | author_email='yusuf.mahdi@gmail.com', 66 | install_requires=dependencies, 67 | tests_require=['tox==1.3'], 68 | packages=['octogit', ], 69 | license='MIT License', 70 | long_description=open('README.rst').read(), 71 | entry_points={ 72 | 'console_scripts': [ 73 | 'octogit = octogit.cli:begin', 74 | ], 75 | }, 76 | classifiers=( 77 | 'Development Status :: 4 - Beta', 78 | 'Intended Audience :: Developers', 79 | 'Natural Language :: English', 80 | 'License :: OSI Approved :: MIT License', 81 | 'Programming Language :: Python', 82 | 'Programming Language :: Python :: 2.6', 83 | 'Programming Language :: Python :: 2.7', 84 | 'Programming Language :: Python :: 3', 85 | ), 86 | ) 87 | -------------------------------------------------------------------------------- /tests/cli.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | import unittest 9 | 10 | class SimpleTest(unittest.TestCase): 11 | def test_basic_addition(self): 12 | """ 13 | Tests that 1 + 1 always equals 2. 14 | """ 15 | self.assertEqual(1 + 1, 2) 16 | -------------------------------------------------------------------------------- /tests/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | import unittest 9 | 10 | class SimpleTest(unittest.TestCase): 11 | def test_basic_addition(self): 12 | """ 13 | Tests that 1 + 1 always equals 2. 14 | """ 15 | self.assertEqual(1 + 1, 2) 16 | -------------------------------------------------------------------------------- /tests/core.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | import unittest 9 | from octogit.core import get_single_issue 10 | 11 | class UTF8Support(unittest.TestCase): 12 | 13 | def assertNotRaises(self, exception_type, called_func, kwargs): 14 | try: 15 | called_func(**kwargs) 16 | except Exception as e: 17 | if isinstance(e, exception_type): 18 | self.fail(e) 19 | else: 20 | pass 21 | 22 | def test_assert_not_raises_UnicodeEncodeError(self): 23 | self.assertNotRaises(UnicodeEncodeError, get_single_issue, 24 | kwargs={'user':'cesarFrias', 'repo':'pomodoro4linux', 25 | 'number':2}) 26 | 27 | 28 | if __name__ == '__main__': 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myusuf3/octogit/4eebd844e44c4b8da3f49bac0e70fc12375c2f94/tox.ini --------------------------------------------------------------------------------