Merge pull request #34 from homeworkprod/cleanup

Python 3 fixes, refactoring, cleanup, and a first test.
This commit is contained in:
Eric Romano
2016-06-09 14:52:11 -04:00
3 changed files with 264 additions and 161 deletions

View File

@ -6,15 +6,32 @@
""" """
gitfiti gitfiti
noun : Carefully crafted graffiti in a github commit history calendar noun : Carefully crafted graffiti in a GitHub commit history calendar
""" """
import datetime from datetime import datetime, timedelta
import math
import itertools import itertools
import urllib2
import json import json
import math
try:
# Python 3+
from urllib.error import HTTPError, URLError
from urllib.request import urlopen
except ImportError:
# Python 2
from urllib2 import HTTPError, URLError, urlopen
try:
# Python 2
raw_input
except NameError:
# Python 3 (Python 2's `raw_input` was renamed to `input`)
raw_input = input
GITHUB_BASE_URL = 'https://github.com/'
FALLBACK_IMAGE = 'kitty'
TITLE = ''' TITLE = '''
_ __ _____ __ _ _ __ _____ __ _
@ -25,6 +42,7 @@ TITLE = '''
/____/ /____/
''' '''
KITTY = [ KITTY = [
[0,0,0,4,0,0,0,0,4,0,0,0], [0,0,0,4,0,0,0,0,4,0,0,0],
[0,0,4,2,4,4,4,4,2,4,0,0], [0,0,4,2,4,4,4,4,2,4,0,0],
@ -32,7 +50,8 @@ KITTY = [
[2,2,4,2,4,2,2,4,2,4,2,2], [2,2,4,2,4,2,2,4,2,4,2,2],
[0,0,4,2,2,3,3,2,2,4,0,0], [0,0,4,2,2,3,3,2,2,4,0,0],
[2,2,4,2,2,2,2,2,2,4,2,2], [2,2,4,2,2,2,2,2,2,4,2,2],
[0,0,0,3,4,4,4,4,3,0,0,0]] [0,0,0,3,4,4,4,4,3,0,0,0],
]
ONEUP = [ ONEUP = [
[0,4,4,4,4,4,4,4,0], [0,4,4,4,4,4,4,4,0],
@ -41,7 +60,8 @@ ONEUP = [
[4,3,4,4,4,4,4,3,4], [4,3,4,4,4,4,4,3,4],
[4,4,1,4,1,4,1,4,4], [4,4,1,4,1,4,1,4,4],
[0,4,1,1,1,1,1,4,0], [0,4,1,1,1,1,1,4,0],
[0,0,4,4,4,4,4,0,0]] [0,0,4,4,4,4,4,0,0],
]
ONEUP2 = [ ONEUP2 = [
[0,0,4,4,4,4,4,4,4,0,0], [0,0,4,4,4,4,4,4,4,0,0],
@ -50,7 +70,8 @@ ONEUP2 = [
[4,3,3,4,4,4,4,4,3,3,4], [4,3,3,4,4,4,4,4,3,3,4],
[0,4,4,1,4,1,4,1,4,4,0], [0,4,4,1,4,1,4,1,4,4,0],
[0,0,4,1,1,1,1,1,4,0,0], [0,0,4,1,1,1,1,1,4,0,0],
[0,0,0,4,4,4,4,4,0,0,0]] [0,0,0,4,4,4,4,4,0,0,0],
]
HACKERSCHOOL = [ HACKERSCHOOL = [
[4,4,4,4,4,4], [4,4,4,4,4,4],
@ -59,7 +80,8 @@ HACKERSCHOOL = [
[4,3,3,3,3,4], [4,3,3,3,3,4],
[4,4,4,4,4,4], [4,4,4,4,4,4],
[0,0,4,4,0,0], [0,0,4,4,0,0],
[4,4,4,4,4,4]] [4,4,4,4,4,4],
]
OCTOCAT = [ OCTOCAT = [
[0,0,0,4,0,0,0,4,0], [0,0,0,4,0,0,0,4,0],
@ -68,7 +90,8 @@ OCTOCAT = [
[4,0,3,4,3,3,3,4,3], [4,0,3,4,3,3,3,4,3],
[0,4,0,0,4,4,4,0,0], [0,4,0,0,4,4,4,0,0],
[0,0,4,4,4,4,4,4,4], [0,0,4,4,4,4,4,4,4],
[0,0,4,0,4,0,4,0,4]] [0,0,4,0,4,0,4,0,4],
]
OCTOCAT2 = [ OCTOCAT2 = [
[0,0,4,0,0,4,0], [0,0,4,0,0,4,0],
@ -77,7 +100,8 @@ OCTOCAT2 = [
[0,4,4,4,4,4,4], [0,4,4,4,4,4,4],
[4,0,0,4,4,0,0], [4,0,0,4,4,0,0],
[0,4,4,4,4,4,0], [0,4,4,4,4,4,0],
[0,0,0,4,4,4,0]] [0,0,0,4,4,4,0],
]
HELLO = [ HELLO = [
[0,1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,4], [0,1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,4],
@ -86,7 +110,8 @@ HELLO = [
[0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,3], [0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,3],
[0,3,0,3,0,3,3,3,0,3,0,3,0,3,0,3,0,2], [0,3,0,3,0,3,3,3,0,3,0,3,0,3,0,3,0,2],
[0,2,0,2,0,2,0,0,0,2,0,2,0,2,0,2,0,0], [0,2,0,2,0,2,0,0,0,2,0,2,0,2,0,2,0,0],
[0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,4]] [0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,4],
]
HIREME = [ HIREME = [
[1,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [1,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
@ -95,32 +120,39 @@ HIREME = [
[4,0,4,0,4,0,4,0,0,0,4,0,4,0,0,4,0,4,0,4,0,4,0,4], [4,0,4,0,4,0,4,0,0,0,4,0,4,0,0,4,0,4,0,4,0,4,0,4],
[3,0,3,0,3,0,3,0,0,0,3,3,3,0,0,3,0,3,0,3,0,3,3,3], [3,0,3,0,3,0,3,0,0,0,3,3,3,0,0,3,0,3,0,3,0,3,3,3],
[2,0,2,0,2,0,2,0,0,0,2,0,0,0,0,2,0,2,0,2,0,2,0,0], [2,0,2,0,2,0,2,0,0,0,2,0,0,0,0,2,0,2,0,2,0,2,0,0],
[1,0,1,0,1,0,1,0,0,0,1,1,1,0,0,1,0,1,0,1,0,1,1,1]] [1,0,1,0,1,0,1,0,0,0,1,1,1,0,0,1,0,1,0,1,0,1,1,1],
]
ASCII_TO_NUMBER = { ASCII_TO_NUMBER = {
'_': 0, '_': 0,
'_': 1, '_': 1,
'~': 2, '~': 2,
'=': 3, '=': 3,
'*': 4 '*': 4,
} }
def str_to_sprite(content): def str_to_sprite(content):
# Break out lines and filter any excess # Break out lines and filter any excess
lines = content.split('\n') lines = content.split('\n')
def is_empty_line(line): def is_empty_line(line):
return len(line) != 0 return len(line) != 0
lines = filter(is_empty_line, lines) lines = filter(is_empty_line, lines)
# Break up lines into each character # Break up lines into each character
split_lines = map(list, lines) split_lines = [list(line) for line in lines]
# Replace each character with its numeric equivalent # Replace each character with its numeric equivalent
for line in split_lines: for line in split_lines:
for index, char in enumerate(line): for index, char in enumerate(line):
line[index] = ASCII_TO_NUMBER.get(char, 0) line[index] = ASCII_TO_NUMBER.get(char, 0)
# Return the formatted str # Return the formatted str
return split_lines return split_lines
ONEUP_STR = str_to_sprite("""
ONEUP_STR = str_to_sprite('''
******* *******
*=~~-~~=* *=~~-~~=*
*~~---~~* *~~---~~*
@ -128,7 +160,8 @@ ONEUP_STR = str_to_sprite("""
**-*-*-** **-*-*-**
*-----* *-----*
***** *****
""") ''')
IMAGES = { IMAGES = {
'kitty': KITTY, 'kitty': KITTY,
@ -139,13 +172,15 @@ IMAGES = {
'octocat2': OCTOCAT2, 'octocat2': OCTOCAT2,
'hello': HELLO, 'hello': HELLO,
'hireme': HIREME, 'hireme': HIREME,
'oneup_str':ONEUP_STR 'oneup_str': ONEUP_STR,
} }
def load_images(img_names): def load_images(img_names):
"""loads user images from given file(s)""" """loads user images from given file(s)"""
if img_names[0] == '': if img_names[0] == '':
return dict() return {}
for image_name in img_names: for image_name in img_names:
img = open(image_name) img = open(image_name)
loaded_imgs = {} loaded_imgs = {}
@ -158,85 +193,108 @@ def load_images(img_names):
img_line = img.readline() img_line = img.readline()
if img_line == '': if img_line == '':
break break
img_line.replace('\n', '') img_line.replace('\n', '')
if(img_line[0] == ':'): if img_line[0] == ':':
loaded_imgs[name] = json.loads(img_list) loaded_imgs[name] = json.loads(img_list)
name = img_line[1:] name = img_line[1:]
img_list = '' img_list = ''
else: else:
img_list += img_line img_list += img_line
loaded_imgs[name] = json.loads(img_list) loaded_imgs[name] = json.loads(img_list)
return loaded_imgs return loaded_imgs
def get_calendar(username, base_url='https://github.com/'):
"""retrieves the github commit calendar data for a username""" def get_calendar(username, base_url):
"""retrieves the GitHub commit calendar data for a username"""
base_url = base_url + 'users/' + username base_url = base_url + 'users/' + username
try: try:
url = base_url + '/contributions' url = base_url + '/contributions'
page = urllib2.urlopen(url) page = urlopen(url)
except (urllib2.HTTPError,urllib2.URLError) as e: except (HTTPError, URLError) as e:
print ("There was a problem fetching data from {0}".format(url)) print('There was a problem fetching data from {0}'.format(url))
print(e) print(e)
raise SystemExit raise SystemExit
return page.readlines()
def max_commits(input): return page.read().decode('utf-8').splitlines()
def find_max_commits(calendar):
"""finds the highest number of commits in one day""" """finds the highest number of commits in one day"""
output = set() output = set()
for line in input:
for line in calendar:
for day in line.split(): for day in line.split():
if "data-count=" in day: if 'data-count=' in day:
commit = day.split('=')[1] commit = day.split('=')[1]
commit = commit.strip('"') commit = commit.strip('"')
output.add(int(commit)) output.add(int(commit))
output = list(output) output = list(output)
output.sort() output.sort()
output.reverse() output.reverse()
return output[0] return output[0]
def multiplier(max_commits):
"""calculates a multiplier to scale github colors to commit history""" def calculate_multiplier(max_commits):
"""calculates a multiplier to scale GitHub colors to commit history"""
m = max_commits / 4.0 m = max_commits / 4.0
if m == 0: return 1
if m == 0:
return 1
m = math.ceil(m) m = math.ceil(m)
m = int(m) m = int(m)
return m return m
def get_start_date(): def get_start_date():
"""returns a datetime object for the first sunday after one year ago today """returns a datetime object for the first sunday after one year ago today
at 12:00 noon""" at 12:00 noon"""
d = datetime.datetime.today() today = datetime.today()
date = datetime.datetime(d.year-1, d.month, d.day, 12) date = datetime(today.year - 1, today.month, today.day, 12)
weekday = datetime.datetime.weekday(date) weekday = datetime.weekday(date)
while weekday < 6: while weekday < 6:
date = date + datetime.timedelta(1) date = date + timedelta(1)
weekday = datetime.datetime.weekday(date) weekday = datetime.weekday(date)
return date return date
def date_gen(start_date, offset=0):
def generate_next_dates(start_date, offset=0):
"""generator that returns the next date, requires a datetime object as """generator that returns the next date, requires a datetime object as
input. The offset is in weeks""" input. The offset is in weeks"""
start = offset * 7 start = offset * 7
for i in itertools.count(start): for i in itertools.count(start):
yield start_date + datetime.timedelta(i) yield start_date + timedelta(i)
def values_in_date_order(image, multiplier=1):
def generate_values_in_date_order(image, multiplier=1):
height = 7 height = 7
width = len(image[0]) width = len(image[0])
for w in range(width): for w in range(width):
for h in range(height): for h in range(height):
yield image[h][w] * multiplier yield image[h][w] * multiplier
def commit(content, commitdate): def commit(content, commitdate):
template = ("""echo {0} >> gitfiti\n""" template = (
"""GIT_AUTHOR_DATE={1} GIT_COMMITTER_DATE={2} """ '''echo {0} >> gitfiti\n'''
"""git commit -a -m "gitfiti" > /dev/null\n""") '''GIT_AUTHOR_DATE={1} GIT_COMMITTER_DATE={2} '''
'''git commit -a -m "gitfiti" > /dev/null\n'''
)
return template.format(content, commitdate.isoformat(), return template.format(content, commitdate.isoformat(),
commitdate.isoformat()) commitdate.isoformat())
def fake_it(image, start_date, username, repo, offset=0, multiplier=1,
git_url='git@github.com'): def fake_it(image, start_date, username, repo, git_url, offset=0, multiplier=1):
template = ('#!/bin/bash\n' template = (
'#!/bin/bash\n'
'REPO={0}\n' 'REPO={0}\n'
'git init $REPO\n' 'git init $REPO\n'
'cd $REPO\n' 'cd $REPO\n'
@ -247,84 +305,102 @@ def fake_it(image, start_date, username, repo, offset=0, multiplier=1,
'{1}\n' '{1}\n'
'git remote add origin {2}:{3}/$REPO.git\n' 'git remote add origin {2}:{3}/$REPO.git\n'
'git pull\n' 'git pull\n'
'git push -u origin master\n') 'git push -u origin master\n'
)
strings = [] strings = []
for value, date in zip(values_in_date_order(image, multiplier), for value, date in zip(generate_values_in_date_order(image, multiplier),
date_gen(start_date, offset)): generate_next_dates(start_date, offset)):
for i in range(value): for i in range(value):
strings.append(commit(i, date)) strings.append(commit(i, date))
return template.format(repo, "".join(strings), git_url, username)
return template.format(repo, ''.join(strings), git_url, username)
def save(output, filename): def save(output, filename):
"""Saves the list to a given filename""" """Saves the list to a given filename"""
f = open(filename, "w") with open(filename, 'w') as f:
f.write(output) f.write(output)
f.close()
def request_user_input(prompt='> '):
"""Request input from the user and return what has been entered."""
return raw_input(prompt)
def main(): def main():
print(TITLE) print(TITLE)
print ("Enter github url")
ghe = raw_input("Enter nothing for https://github.com/ to be used: ")
print ('Enter your github username:')
username = raw_input(">")
if not ghe:
git_base = "https://github.com/"
cal = get_calendar(username)
else:
cal = get_calendar(username,base_url=ghe)
git_base = ghe
m = multiplier(max_commits(cal))
print ('Enter name of the repo to be used by gitfiti:') ghe = request_user_input(
repo = raw_input(">") 'Enter GitHub URL (leave blank to use {}): '.format(GITHUB_BASE_URL))
print ('Enter the number of weeks to offset the image (from the left):') username = request_user_input('Enter your GitHub username: ')
offset = raw_input(">")
if not offset.strip():
offset = 0
else:
offset = int(offset)
print ('By default gitfiti.py matches the darkest pixel to the highest\n' git_base = ghe if ghe else GITHUB_BASE_URL
'number of commits found in your github commit/activity calendar,\n'
cal = get_calendar(username, git_base)
max_commits = find_max_commits(cal)
m = calculate_multiplier(max_commits)
repo = request_user_input(
'Enter the name of the repository to use by gitfiti: ')
offset = request_user_input(
'Enter the number of weeks to offset the image (from the left): ')
offset = int(offset) if offset.strip() else 0
print((
'By default gitfiti.py matches the darkest pixel to the highest\n'
'number of commits found in your GitHub commit/activity calendar,\n'
'\n' '\n'
'Currently this is: {0} commits\n' 'Currently this is: {0} commits\n'
'\n' '\n'
'Enter the word "gitfiti" to exceed your max\n' 'Enter the word "gitfiti" to exceed your max\n'
'(this option generates WAY more commits)\n' '(this option generates WAY more commits)\n'
'Any other input will cause the default matching behavior' 'Any other input will cause the default matching behavior'
).format(max_commits(cal)) ).format(max_commits))
match = raw_input(">") match = request_user_input()
if match == "gitfiti":
match = m
else:
match = 1
print ('enter file(s) to load images from (blank if not applicable)') match = m if (match == 'gitfiti') else 1
img_names = raw_input(">").split(' ')
images = dict(IMAGES, **load_images(img_names)) print('Enter file(s) to load images from (blank if not applicable)')
img_names = request_user_input().split(' ')
loaded_images = load_images(img_names)
images = dict(IMAGES, **loaded_images)
print('Enter the image name to gitfiti')
print('Images: ' + ', '.join(images.keys()))
image = request_user_input()
image_name_fallback = FALLBACK_IMAGE
print ('enter the image name to gitfiti')
print ('images: ' + ", ".join(images.keys()))
image = raw_input(">")
if not image: if not image:
image = IMAGES['kitty'] image = IMAGES[image_name_fallback]
else: else:
try: try:
image = images[image] image = images[image]
except: except:
image = IMAGES['kitty'] image = IMAGES[image_name_fallback]
start_date = get_start_date()
fake_it_multiplier = m * match
if not ghe: if not ghe:
output = fake_it(image, get_start_date(), username, repo, offset, git_url = 'git@github.com'
m*match)
else: else:
git_url = raw_input("Enter git url like git@site.github.com: ") git_url = request_user_input('Enter Git URL like git@site.github.com: ')
output = fake_it(image, get_start_date(), username, repo, offset,
m*match,git_url=git_url) output = fake_it(image, start_date, username, repo, git_url, offset,
fake_it_multiplier)
save(output, 'gitfiti.sh') save(output, 'gitfiti.sh')
print('gitfiti.sh saved.') print('gitfiti.sh saved.')
print ('Create a new(!) repo at: {0}new and run it.'.format(git_base)) print('Create a new(!) repo at {0}new and run the script'.format(git_base))
if __name__ == '__main__': if __name__ == '__main__':
main() main()

0
tests/__init__.py Normal file
View File

View File

@ -0,0 +1,27 @@
from gitfiti import str_to_sprite, ONEUP_STR
SYMBOLS = '''
*******
*=~~-~~=*
*~~---~~*
*=*****=*
**-*-*-**
*-----*
*****
'''
NUMBERS = [
[0,4,4,4,4,4,4,4,0],
[4,3,2,2,0,2,2,3,4],
[4,2,2,0,0,0,2,2,4],
[4,3,4,4,4,4,4,3,4],
[4,4,0,4,0,4,0,4,4],
[0,4,0,0,0,0,0,4,0],
[0,0,4,4,4,4,4,0,0],
]
def test_symbols_to_numbers():
actual = str_to_sprite(SYMBOLS)
assert actual == NUMBERS