├── .gitignore ├── .pyg-submitted ├── .travis.yml ├── LICENSE ├── MANIFEST ├── MANIFEST.in ├── README.md ├── pyg ├── Pygemony.py ├── __init__.py ├── github.py ├── languages.py ├── run.py └── utils.py ├── requirements ├── all.txt └── test.txt ├── setup.py └── unit_tests ├── __init__.py └── test_pygemony.py /.gitignore: -------------------------------------------------------------------------------- 1 | dist* 2 | build* 3 | .idea* 4 | *.pyc 5 | *.swp 6 | .pypirc 7 | *.egg-info 8 | .coverage 9 | htmlcov/ 10 | -------------------------------------------------------------------------------- /.pyg-submitted: -------------------------------------------------------------------------------- 1 | a36ef6c6cd656f2e83a11215137c43a7 2 | cc4134f0937760a8fe9a4d17fe414d74 3 | f19e32b950b042e89be681e57da480aa 4 | 0f13516d300b7f1390f43457ac8deece 5 | c42c74a25638c8957b3b391f2ecf209f 6 | a36ef6c6cd656f2e83a11215137c43a7 7 | cc4134f0937760a8fe9a4d17fe414d74 8 | 02a74899f8fa61854f41fe8e950c1da5 9 | 0f13516d300b7f1390f43457ac8deece 10 | c42c74a25638c8957b3b391f2ecf209f 11 | a36ef6c6cd656f2e83a11215137c43a7 12 | cc4134f0937760a8fe9a4d17fe414d74 13 | 0f13516d300b7f1390f43457ac8deece 14 | c42c74a25638c8957b3b391f2ecf209f 15 | 93246468a3d48046db39e0d75cf852cf 16 | 03952f5ae6b26054bac1d96ddf2d4983 17 | 6918e88b59e6788429399be71759714f 18 | 57736eacd0e3e3c21d72a0f490041588 19 | c5f40f2d7f08a72ca1ce15dff8f59845 20 | e2b62964f3586b6a0dbe4de39ef0d0fe 21 | 0956a1c9e97610e48edf4a53c66ab550 22 | 8e58ee5ef87e6db145e8c9d0de346f49 23 | 452d8817fdc00c48783c82b46007e3e2 24 | 95cfcecec833acc232a6c9c7e7e1fdaa 25 | f04e7e66e26839a1ecc79606e8ffb3a4 26 | 397a473f6e1446d92288300f4c9ff7d8 27 | 50e4664783861f1248e7180a0786d9ae 28 | 5b807aa092619103631d91260f61f835 29 | f6a550af2efbab3944689f7b4a45b2e9 30 | 20e819f517f8f0800e0d45e61c02a2ab 31 | e543e732987a75f90b15cd17470d0c92 32 | dbd5b518eba8407c0a39e414a13353e5 33 | b3fd0fd5f5d195c196f982ad6873e222 34 | e0023e3cf20b5352ad190c45ba82a1e5 35 | 2a779286389eea3dc7ca56054df804b8 36 | 87e5c08080d86edc864f3736c8d05add 37 | d84c88b5eebe2b4a9b8592929c93d944 38 | 336e59e937032e9014f83f9b9b348c3a 39 | 45f9f3043a437a3e2661ee0aef565e4f 40 | b3a97475e6f9d959d263b8e753eb2575 41 | a8d143ef65870400cd3cc795ea1798e5 42 | d09f2212d5b33025281d4ee101d0d697 43 | b98ad81b4378a8d096cc4107c20b8365 44 | 8ad9142b8162600f763f4bf601070acf 45 | b7c907c1ee07e7dcf505a4cbdea6b566 46 | ebbe099020c1f30408404021c4898b51 47 | 09e0c22b76d9d2d7f622ca59a02961dc 48 | 05190b1b2098dba1d0f2f902ec53360d 49 | 618dbd193e3001853ae701a59a2cd5da 50 | 16d000930456f4a023f6cc1facaeace2 51 | a9f35f848516b5a39eb35b0c34731748 52 | 1b83c0cc6252b09465ee83ccf2c9a15b 53 | e927d78e5a2e37fd30988431191293f5 54 | 0d29984f0c3b106be865178a02812b0e 55 | 586f974d07fc3cf593bca151584d5c7e 56 | 1707ec22857058433cd344aeec14607b 57 | 97980ed0a975a94965658e7cc6c03fd9 58 | 5ec0c9a5e3cdc8320da1f9b9a116764f 59 | ff863a8d84ea2d1760c42e07f776b3d9 60 | 6779d874227d4c0467912968d71b979b 61 | 409579a99215a902294e70e756bbfc97 62 | 499b12aee1f529467f4182bb1dedafa2 63 | 6a8c481972ab0c2bdaa1843a3ab95ea6 64 | 3ca3ee503081b1b870ce25f4e2772229 65 | 05296ebeab13cec609aa743cbd472508 66 | 923d14b156ab79bce638fe56703bc1a6 67 | 831bff0a9708a3b05eb8cd141b19df1f 68 | df71a00e1b7951c56e82018d1be14c84 69 | 3af4227317ca0b1e8f8e082d58e521f1 70 | 4b734a6a0e59e55ce9ee9bf21a5e38e3 71 | b7f93fe0974650a90ff8744372f21de5 72 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | cache: pip 3 | python: 4 | - "2.6" 5 | - "2.7" 6 | 7 | before_script: 8 | - pip install -r requirements/test.txt 9 | - echo $TEST 10 | script: 11 | - nosetests --with-coverage --cover-branches --cover-package pyg 12 | after_success: 13 | - coveralls 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ian Clark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | LICENSE 3 | README 4 | pygemony.py 5 | setup.py 6 | pyg\Pygemony.py 7 | pyg\__init__.py 8 | pyg\github.py 9 | pyg\languages.py 10 | pyg\utils.py 11 | 12 | # Added by ian 13 | include *.md 14 | include MANIFEST 15 | 16 | # misc 17 | include LICENSE 18 | recursive-include unit_tests test_*.py 19 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README LICENSE 2 | 3 | # Added by ian 4 | include *.md 5 | include *.txt 6 | include MANIFEST 7 | 8 | # misc 9 | include LICENSE 10 | recursive-include unit_tests *.py 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/GrappigPanda/pygemony.svg?branch=master)](https://travis-ci.org/GrappigPanda/pygemony) 2 | [![Code Health](https://landscape.io/github/GrappigPanda/pygemony/master/landscape.svg)](https://landscape.io/github/GrappigPanda/pygemony/master) 3 | [![Coverage Status](http://img.shields.io/coveralls/GrappigPanda/pygemony/master.svg)](https://coveralls.io/r/GrappigPanda/pygemony) 4 | 5 | [![PyPI Py Versions](http://badge.kloud51.com/pypi/py_versions/pygemony.svg)](https://pypi.python.org/pypi/pygemony) 6 | [![GitHub license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://pypi.python.org/pypi/pygemony) 7 | 8 | # Pygemony 9 | 10 | If you're anything like me, you're lazy... and then you're forgetful. 11 | By that I mean that you mean to write things (in the future), so you write down 12 | a nifty little #TODO(name) (Or however you comment in your language of choice). 13 | After that, however, you get so caught up in other things that you forget about 14 | implementing these features later on. Not any longer! 15 | 16 | You see, I created Pygemony so that I could run Pygemony and instantly have 17 | the todos be created as issues on my Github page. This way, I can always know 18 | what I forgot to implement. 19 | 20 | Moreover, Pygemony won't spam your issues page, as it hashes and saves these 21 | stored todos into a .pyg-submitted into your git repository. 22 | 23 | ### Running pygemony is really simple! 24 | Naturally, the first step is to get your hands on a copy of it: 25 | 26 | Using pip: 27 | ```python 28 | pip install pygemony==0.4.2 29 | ``` 30 | 31 | 32 | After you've gotten a copy, there's one more thing you need to do: Generate 33 | a Github OAUTH token. 34 | 35 | You can read up more about oauth tokens here: 36 | https://help.github.com/articles/creating-an-access-token-for-command-line-use/ 37 | 38 | Okay, since you've got yourself a copy of Pygemony and an OAuth token, you're 39 | ready to roll. Example usage of Pygemony: 40 | ``` 41 | pygemony --username USERNAME --token GITHUB_TOKEN 42 | ``` 43 | 44 | Whenever I run it, it looks like so: 45 | ``` 46 | pygemony --username GrappigPanda --token $GITHUB_TOKEN 47 | ``` 48 | (I find it nice and easy to set an environmental variable $GITHUB_TOKEN, not 49 | necessary at all!) 50 | 51 | Pygemony should take care of all of the extra work after this and detect where 52 | to open the issues. 53 | 54 | If, however, you want Pygemony to report to somewhere else, you can specify by 55 | adding additional command-line arguments: 56 | ``` 57 | --owner: The owner of the repo (think GrappigPanda) 58 | --repo: The repo's name (think Pygemony) 59 | ``` 60 | 61 | Whenever you inevitably run into bugs because I'm dumb and don't follow best 62 | practices, feel free to open a Github issue and yell and scream at me. But 63 | please don't actually yell and scream at me because that's demotivational and 64 | no one wants that. 65 | 66 | ### LIVE EXAMPLES: 67 | https://github.com/GrappigPanda/pygemony/issues 68 | 69 | https://github.com/GrappigPanda/GithubTODOScraper/issues 70 | 71 | As this project currently stands, I do NOT consider it complete and I consider 72 | it in very early alpha stages. I have a list of issues available on the 73 | project's github page [Pygemony] which I'm more 74 | than happy to receive help with. 75 | 76 | [Pygemony]: http://github.com/GrappigPanda/pygemony 77 | 78 | ### Languages Supported 79 | C 80 | C++ 81 | Python (naturally :) 82 | Javascript 83 | 84 | -------------------------------------------------------------------------------- /pyg/Pygemony.py: -------------------------------------------------------------------------------- 1 | from fnmatch import filter as fn_filter 2 | from os import walk, path 3 | import hashlib 4 | 5 | from utils import detect_mimetype 6 | from github import GithubAPIManager 7 | from languages import * 8 | 9 | 10 | class Pygemony(object): 11 | """ 12 | The main driver of pygemony, pulls the seperate pieces together. 13 | """ 14 | def __init__(self, user=None, token=None, owner=None, repo=None): 15 | # todo_found contains a list of the following layout: 16 | # ['file_path', 'line_number', 'todo_message', 'md5 of todo'] 17 | self.blacklist = ['build', '.git'] 18 | self.todo_found = [] 19 | self.github = GithubAPIManager(user, token, owner, repo) 20 | # TODO(ian): Add support for parsing more than one file type 21 | self.language = self.lookup_language() 22 | 23 | def _sanitize_todo_line(self, lines): 24 | """ 25 | Strips tab, newline, and comment characters form the TODO line. 26 | 27 | :param str lines: The found line containing a TODO 28 | :rtype: str 29 | :return: The sanitized TODO line. 30 | """ 31 | # We're mainly aiming to remove newlines and tab characters here. 32 | lines = lines.replace('\n', '') 33 | while ' ' in lines or '\t' in lines: 34 | lines = lines.replace(' ', '') 35 | for lang in self.language: 36 | lines = lines.replace(lang.single_comment, '') 37 | return lines 38 | 39 | @staticmethod 40 | def hash_todo(todo_content, file_name): 41 | """ 42 | Hashes the TODO line with the file name 43 | 44 | :param str todo_content: The line in the file containing TODO 45 | :param str file_name: The file name containing the TODO line. 46 | :rtype: str 47 | :return: The MD5 hash of the `todo_content` and `file_name` 48 | """ 49 | m = hashlib.md5() 50 | m.update('{0}-{1}'.format(todo_content, file_name)) 51 | return str(m.hexdigest()) 52 | 53 | def parse_for_todo(self, f, file_): 54 | """ 55 | Searches (line-by-line) through a file's content and and looks for 56 | lines containing TODO. 57 | 58 | :param file_handle f: The handle to the file that is currently being 59 | searched 60 | :param str file_: The name of the file currently being searched 61 | """ 62 | for i, line in enumerate(f.readlines()): 63 | if "TODO" in line and self._starts_with_comment(line): 64 | line = self._sanitize_todo_line(line) 65 | self.todo_found.append([file_, i, line, self.hash_todo(line, file_)]) 66 | 67 | def parse_by_extension(self, files): 68 | """ 69 | Parses the list of the directory for files with an acceptable 70 | extension. The extension is determined by data returned from github on 71 | the languages used in the project. 72 | 73 | :param list files: The list of all files in the current repository 74 | :rtype: generator(str) 75 | :return: Generates a list of acceptable-to-parse files. 76 | """ 77 | for lang in self.language: 78 | for ext in lang.file_exts: 79 | for file_ in fn_filter(files, ext): 80 | yield file_ 81 | 82 | def find_all_files(self, root): 83 | """ 84 | Walks the current repository directory and determines viable files 85 | 86 | :param str root: The root directory 87 | :rtype: list 88 | :return: The list of files found and determined to be viable. 89 | """ 90 | files_found = [] 91 | 92 | for roots, _, files in walk(root): 93 | base_dir = roots.split('/')[1] 94 | 95 | if base_dir not in self.blacklist: 96 | for file_ in self.parse_by_extension(files): 97 | files_found.append(path.join(roots, file_)) 98 | 99 | return files_found 100 | 101 | def file_handler(self): 102 | """ 103 | Handles IO with the file 104 | 105 | :rtype: list 106 | :return: The list of files found 107 | """ 108 | # First we need to remove any non-text files 109 | files_found = self.find_all_files('./') 110 | # TODO(ian): filter() over files to parse out by mimetype 111 | for file_ in files_found: 112 | file_type = detect_mimetype(file_) 113 | # We're looking for startswith('text/'). Mimetype returns 114 | # None if it can't determine file type. Remove if either is True 115 | try: 116 | if file_type[0].startswith("application") or file_type[0] is None: 117 | files_found.remove(file_) 118 | except (AttributeError, IndexError) as e: 119 | print "Failed to open file {} with error of {}".format(file_, e) 120 | 121 | for file_ in files_found: 122 | try: 123 | with open(file_, 'r') as f: 124 | self.parse_for_todo(f, file_) 125 | except IOError as e: 126 | print "Failed to open file {} with error of {}".format(file_, e) 127 | 128 | return files_found 129 | 130 | def run(self): 131 | """ 132 | Starts the process of finding TODOs 133 | """ 134 | self.file_handler() 135 | self.github.commit(self.todo_found) 136 | 137 | def lookup_language(self): 138 | """ 139 | Constructs langauge classes based on what is found in github data. 140 | 141 | :rtype: list 142 | :return: A list of language classes that will be found in a github 143 | repo. 144 | """ 145 | lang_map = {'cpp': LanguageCPP, 146 | 'python': LanguagePython, 147 | 'javascript': LanguageJavascript, 148 | 'c': LanguageC, 149 | 'go': LanguageGo, 150 | 'erlang': LanguageErlang} 151 | langs = [i for i in self.github.get_languages()] 152 | 153 | for i in langs: 154 | self.blacklist.append(lang_map[str(langs[0][0]).lower()]().ignore_dir) 155 | 156 | return [lang_map[str(langs[0][0]).lower()]()] 157 | 158 | def _starts_with_comment(self, line): 159 | """ 160 | Verifies a line (containing the word TODO) starts with a comment, if it 161 | does, we deem it to be commit-viable. 162 | 163 | :param str line: The line that contains "TODO" 164 | 165 | :rtype: bool 166 | :return: True if line starts with a comment (is a valid TODO statement) 167 | """ 168 | comments = self._create_comment_start_list() 169 | for comment in comments: 170 | if line.startswith(comment): 171 | return True 172 | 173 | def _create_comment_start_list(self): 174 | """ 175 | Create a list of comments from each language class associated with the 176 | current repo. 177 | 178 | :rtype: list 179 | :return: A list of strings containing all line-start comments. 180 | """ 181 | comments = [] 182 | for lang in self.language: 183 | comments.append(lang.single_comment) 184 | comments.append(lang.multi_comment[0]) 185 | return comments 186 | -------------------------------------------------------------------------------- /pyg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ianleeclark/pygemony/8501609300ca1881e8cd6465002336405a19d910/pyg/__init__.py -------------------------------------------------------------------------------- /pyg/github.py: -------------------------------------------------------------------------------- 1 | import github3 2 | from os import path 3 | 4 | 5 | class GithubAPIManager(object): 6 | """ 7 | Handles authorization and everything else done with the github api. 8 | """ 9 | 10 | # repo_location = grappigpanda/pygemony.py 11 | def __init__(self, user, token, owner, repo): 12 | self.is_authed = False 13 | 14 | # Auth stuff 15 | self.user = user 16 | self.token = token 17 | self.gh = self.login() 18 | if self.gh is None: 19 | raise Exception("Failed to login") 20 | 21 | # Remote repo stuff 22 | if not owner: 23 | self.owner = self.get_owner().rstrip() 24 | else: 25 | self.owner = owner 26 | if not repo: 27 | self.repo = self.get_repo().rstrip() 28 | else: 29 | self.repo = repo 30 | self.curr_repo = self.gh.repository(str(self.owner), str(self.repo)) 31 | 32 | 33 | def login(self): 34 | """ 35 | Logs the user in. 36 | 37 | :rtype: gh.repository 38 | :return: github3.login() 39 | """ 40 | try: 41 | return github3.login(self.user, self.token) 42 | except github3.models.GitHubError as e: 43 | print "Failed to login due to {}".format(e) 44 | return None 45 | 46 | def _save_submitted_todo(self, issue): 47 | """ 48 | Writes the TODO issue information (the hashed info) to the 49 | `.pyg-submitted` file. 50 | 51 | :param list issue: A list containing info about the found TODO 52 | :rtype: bool 53 | :return: True on successful write, False if the issue exists already. 54 | """ 55 | if not path.isfile('./.pyg-submitted'): 56 | with open('./.pyg-submitted', 'a+') as f: 57 | f.write(""" 58 | This file was auto-generated by pygemony to help users keep-track of long-forgotten TODOs. 59 | For more information, please visit: https://github.com/GrappigPanda/pygemony 60 | """) 61 | f.write(issue[3]) 62 | f.write('\n') 63 | return True 64 | 65 | if issue[3] in open('./.pyg-submitted').read(): 66 | return False 67 | else: 68 | with open('./.pyg-submitted', 'ab') as f: 69 | f.write(issue[3]) 70 | f.write('\n') 71 | return True 72 | 73 | def commit(self, todo_found): 74 | """ 75 | Creates a github issue per TODO found. 76 | 77 | 78 | :param list todo_found: A list containing information about the found 79 | TODO 80 | """ 81 | # TODO(ian): Assign issues if () in line. (ian), for example 82 | for issue in todo_found: 83 | if self._save_submitted_todo(issue): 84 | self._pprint(issue) 85 | self.curr_repo.create_issue(title=issue[2], 86 | body=self._construct_issue_body(issue)) 87 | 88 | def get_languages(self): 89 | """ 90 | Iterates through the languages used in the project and yields the 91 | language. 92 | 93 | :rtype: generator(str) 94 | :return: A generator for the languages used in the project 95 | """ 96 | for i in self.curr_repo.iter_languages(): 97 | yield i 98 | 99 | def _get_repo_owner(self): 100 | """ 101 | Gets the repo owner from the gitconfig. 102 | 103 | :rtype: tuple 104 | :return: The (repo, owner) of the repo from the git config. 105 | """ 106 | # TODO(ian): Remove the magic directory! 107 | with open('./.git/config', 'r+') as f: 108 | for line in f.readlines(): 109 | if 'url = ' in line: 110 | return line.split('github.com/')[-1].split('/') 111 | 112 | def get_repo(self): 113 | """ 114 | Reads the repo name from the git config 115 | 116 | :rtype: str 117 | :return: The repo's name 118 | """ 119 | return self._get_repo_owner()[1] 120 | 121 | def get_owner(self): 122 | """ 123 | Reads the repo owner's name from the git config 124 | 125 | :rtype: str 126 | :return: The repo owner's name 127 | """ 128 | return self._get_repo_owner()[0] 129 | 130 | @staticmethod 131 | def _construct_issue_body(issue): 132 | """ 133 | Constructs the issue body that is posted in the github issue. 134 | 135 | :rtype: str 136 | :return: The issue body, html formatted. 137 | """ 138 | # TODO(ian): Move all @staticmethods to a seperate class 139 | sz = 'File Location: {}
Line Number: {}'.format(issue[0], issue[1]) 140 | sz += '
This message was auto-generated by Pygemony: ' 141 | sz += 'Github' 142 | return sz 143 | 144 | def _pprint(self, issue): 145 | """ 146 | Prints information to the screen about creating commits 147 | 148 | :param list issue: A list of information about the found TODO. 149 | """ 150 | msg = "Committing to repo: {}" 151 | msg += "\n\tFile Name: {}:{}\n\tTodo Message:{}" 152 | print msg.format(self.repo, issue[0], issue[1], issue[2]) 153 | 154 | -------------------------------------------------------------------------------- /pyg/languages.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Ian' 2 | 3 | 4 | class LanguageCPP(object): 5 | def __init__(self): 6 | self.single_comment = '//' 7 | self.multi_comment = ['/*', '*/'] 8 | self.file_exts = ['*.cpp', '*.cxx', '*.c', '*.hpp', '*.hxx', '*.h'] 9 | self.ignore_dir = [] 10 | 11 | 12 | class LanguageC(object): 13 | def __init__(self): 14 | self.single_comment = '//' 15 | self.multi_comment = ['/*', '*/'] 16 | self.file_exts = ['*.c', '*.h'] 17 | self.ignore_dir = [] 18 | 19 | 20 | class LanguagePython(object): 21 | def __init__(self): 22 | self.single_comment = '#' 23 | self.multi_comment = ['"""', '"""'] 24 | # How does iron python, stackless, &c do it? 25 | self.file_exts = ['*.py'] 26 | self.ignore_dir = [] 27 | 28 | 29 | class LanguageJavascript(object): 30 | def __init__(self): 31 | self.single_comment = '//' 32 | self.multi_comment = ['/*', '*/'] 33 | self.file_exts = ['*.js', '.node'] 34 | self.ignore_dir = ['node_modules'] 35 | self.ignore_dir = [] 36 | 37 | class LanguageGo(object): 38 | def __init__(self): 39 | self.single_comment = '//' 40 | self.multi_comment = ['/*', '*/'] 41 | self.file_exts = ['*.go'] 42 | self.ignore_dir = [] 43 | 44 | class LanguageErlang(object): 45 | def __init__(self): 46 | self.single_comment = '%' 47 | self.multi_comment = [] 48 | self.file_exts = ['*.erl', '*.hrl'] 49 | self.ignore_dir = [] 50 | 51 | -------------------------------------------------------------------------------- /pyg/run.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from pyg.Pygemony import Pygemony 4 | 5 | def main(): 6 | parser = argparse.ArgumentParser() 7 | 8 | parser.add_argument('--token', required=True) 9 | parser.add_argument('--username', required=True) 10 | parser.add_argument('--owner') 11 | parser.add_argument('--repo') 12 | 13 | args = parser.parse_args() 14 | args = vars(args) 15 | 16 | pygemony = Pygemony(args.get('username'), 17 | args.get('token'), 18 | args.get('owner'), 19 | args.get('repo')) 20 | pygemony.run() 21 | -------------------------------------------------------------------------------- /pyg/utils.py: -------------------------------------------------------------------------------- 1 | from mimetypes import guess_type 2 | 3 | def get_git_info(): 4 | """ 5 | Parses the git info and returns a tuple containg the owner and repo 6 | 7 | :deprecated: 8 | :rtype: tuple 9 | :return: (owner name, repo name) 10 | """ 11 | repo = '' 12 | with open('.git/config') as f: 13 | for line in f.readlines(): 14 | if 'url' in line: 15 | repo = line.replace('url = ', '').strip() 16 | r = repo.split('/') 17 | # Return a tuple containing the owner and the repo name 18 | return r[-2], r[-1] 19 | 20 | 21 | def detect_mimetype(file_): 22 | """ 23 | Detects the provided file's mimetype. Used to determine if we should read 24 | the file line-by-line. 25 | 26 | :param str file_: The name of the file to guess the mimetype of 27 | :rtype: str 28 | :return: The mimetype of the file provided 29 | """ 30 | return guess_type(file_) 31 | -------------------------------------------------------------------------------- /requirements/all.txt: -------------------------------------------------------------------------------- 1 | github3.py==0.9.5 2 | requests==2.9.1 3 | uritemplate.py==0.3.0 4 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | -r all.txt 2 | 3 | mock==1.3.0 4 | nose==1.3.7 5 | coverage==4.0.3 6 | coveralls 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | # 5 | CLASSIFIERS = [ 6 | "Intended Audience :: Developers", 7 | "Natural Language :: English", 8 | "Operating System :: OS Independent", 9 | "Programming Language :: Python", 10 | "Programming Language :: Python", 11 | "Programming Language :: Python :: 2", 12 | "Programming Language :: Python :: 2.6", 13 | "Programming Language :: Python :: 2.7" 14 | ] 15 | # 16 | 17 | def read(fname): 18 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 19 | 20 | setup( 21 | name='pygemony', 22 | install_requires=read('requirements/all.txt'), 23 | version='0.4.2', 24 | description=('Parse long-forgotten TODO messages from Github Repos and create Issues to resolve.'), 25 | license=read("LICENSE"), 26 | author='Ian Lee Clark', 27 | author_email='ian@ianleeclark.com', 28 | packages=['pyg'], 29 | url='https://github.com/GrappigPanda/pygemony', 30 | classifiers=CLASSIFIERS, 31 | entry_points={ 32 | 'console_scripts': [ 33 | 'pygemony = pyg.run:main', 34 | ], 35 | } 36 | ) 37 | -------------------------------------------------------------------------------- /unit_tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /unit_tests/test_pygemony.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | 3 | import unittest 4 | 5 | from pyg.Pygemony import Pygemony 6 | 7 | class PygemonyTestCase(unittest.TestCase): 8 | 9 | def test_hashing_pass(self): 10 | md5 = Pygemony.hash_todo("# TODO(ian): Testing 123", 'test.py') 11 | self.assertEqual(md5, "8f83bdfe5ce85ac91d3e84e879fce24e") 12 | --------------------------------------------------------------------------------