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,102 +42,117 @@ 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],
[0,0,4,2,2,2,2,2,2,4,0,0], [0,0,4,2,2,2,2,2,2,4,0,0],
[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],
[4,3,2,2,1,2,2,3,4], [4,3,2,2,1,2,2,3,4],
[4,2,2,1,1,1,2,2,4], [4,2,2,1,1,1,2,2,4],
[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],
[0,4,2,2,1,1,1,2,2,4,0], [0,4,2,2,1,1,1,2,2,4,0],
[4,3,2,2,1,1,1,2,2,3,4], [4,3,2,2,1,1,1,2,2,3,4],
[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],
[4,3,3,3,3,4], [4,3,3,3,3,4],
[4,1,3,3,1,4], [4,1,3,3,1,4],
[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],
[0,0,4,4,4,4,4,4,4], [0,0,4,4,4,4,4,4,4],
[0,0,4,1,3,3,3,1,4], [0,0,4,1,3,3,3,1,4],
[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],
[0,4,4,4,4,4,4], [0,4,4,4,4,4,4],
[0,4,1,3,3,1,4], [0,4,1,3,3,1,4],
[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],
[0,2,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,4], [0,2,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,4],
[0,3,3,3,0,2,3,3,0,3,0,3,0,1,3,1,0,3], [0,3,3,3,0,2,3,3,0,3,0,3,0,1,3,1,0,3],
[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],
[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[3,3,3,0,2,0,3,3,3,0,2,3,3,0,0,3,3,0,3,0,0,2,3,3], [3,3,3,0,2,0,3,3,3,0,2,3,3,0,0,3,3,0,3,0,0,2,3,3],
[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,24 +160,27 @@ ONEUP_STR = str_to_sprite("""
**-*-*-** **-*-*-**
*-----* *-----*
***** *****
""") ''')
IMAGES = { IMAGES = {
'kitty': KITTY, 'kitty': KITTY,
'oneup':ONEUP, 'oneup': ONEUP,
'oneup2':ONEUP2, 'oneup2': ONEUP2,
'hackerschool':HACKERSCHOOL, 'hackerschool': HACKERSCHOOL,
'octocat':OCTOCAT, 'octocat': OCTOCAT,
'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):
m = max_commits/4.0 """calculates a multiplier to scale GitHub colors to commit history"""
if m == 0: return 1 m = max_commits / 4.0
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'
'\n'
'Currently this is : {0} commits\n'
'\n'
'Enter the word "gitfiti" to exceed your max\n'
'(this option generates WAY more commits)\n'
'Any other input will cause the default matching behavior'
).format(max_commits(cal))
match = raw_input(">")
if match == "gitfiti":
match = m
else:
match = 1
print ('enter file(s) to load images from (blank if not applicable)') cal = get_calendar(username, git_base)
img_names = raw_input(">").split(' ')
images = dict(IMAGES, **load_images(img_names)) 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'
'Currently this is: {0} commits\n'
'\n'
'Enter the word "gitfiti" to exceed your max\n'
'(this option generates WAY more commits)\n'
'Any other input will cause the default matching behavior'
).format(max_commits))
match = request_user_input()
match = m if (match == 'gitfiti') else 1
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