├── .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 | [](https://travis-ci.org/GrappigPanda/pygemony)
2 | [](https://landscape.io/github/GrappigPanda/pygemony/master)
3 | [](https://coveralls.io/r/GrappigPanda/pygemony)
4 |
5 | [](https://pypi.python.org/pypi/pygemony)
6 | [](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 |
--------------------------------------------------------------------------------