Merge pull request #34 from homeworkprod/cleanup
Python 3 fixes, refactoring, cleanup, and a first test.
This commit is contained in:
262
gitfiti.py
262
gitfiti.py
@ -6,15 +6,32 @@
|
||||
"""
|
||||
gitfiti
|
||||
|
||||
noun : Carefully crafted graffiti in a github commit history calendar
|
||||
|
||||
noun : Carefully crafted graffiti in a GitHub commit history calendar
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import math
|
||||
from datetime import datetime, timedelta
|
||||
import itertools
|
||||
import urllib2
|
||||
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 = '''
|
||||
_ __ _____ __ _
|
||||
@ -25,6 +42,7 @@ TITLE = '''
|
||||
/____/
|
||||
'''
|
||||
|
||||
|
||||
KITTY = [
|
||||
[0,0,0,4,0,0,0,0,4,0,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],
|
||||
[0,0,4,2,2,3,3,2,2,4,0,0],
|
||||
[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 = [
|
||||
[0,4,4,4,4,4,4,4,0],
|
||||
@ -41,7 +60,8 @@ ONEUP = [
|
||||
[4,3,4,4,4,4,4,3,4],
|
||||
[4,4,1,4,1,4,1,4,4],
|
||||
[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 = [
|
||||
[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],
|
||||
[0,4,4,1,4,1,4,1,4,4,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 = [
|
||||
[4,4,4,4,4,4],
|
||||
@ -59,7 +80,8 @@ HACKERSCHOOL = [
|
||||
[4,3,3,3,3,4],
|
||||
[4,4,4,4,4,4],
|
||||
[0,0,4,4,0,0],
|
||||
[4,4,4,4,4,4]]
|
||||
[4,4,4,4,4,4],
|
||||
]
|
||||
|
||||
OCTOCAT = [
|
||||
[0,0,0,4,0,0,0,4,0],
|
||||
@ -68,7 +90,8 @@ OCTOCAT = [
|
||||
[4,0,3,4,3,3,3,4,3],
|
||||
[0,4,0,0,4,4,4,0,0],
|
||||
[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 = [
|
||||
[0,0,4,0,0,4,0],
|
||||
@ -77,7 +100,8 @@ OCTOCAT2 = [
|
||||
[0,4,4,4,4,4,4],
|
||||
[4,0,0,4,4,0,0],
|
||||
[0,4,4,4,4,4,0],
|
||||
[0,0,0,4,4,4,0]]
|
||||
[0,0,0,4,4,4,0],
|
||||
]
|
||||
|
||||
HELLO = [
|
||||
[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,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,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 = [
|
||||
[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],
|
||||
[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],
|
||||
[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 = {
|
||||
'_': 0,
|
||||
'_': 1,
|
||||
'~': 2,
|
||||
'=': 3,
|
||||
'*': 4
|
||||
'*': 4,
|
||||
}
|
||||
|
||||
|
||||
def str_to_sprite(content):
|
||||
# Break out lines and filter any excess
|
||||
lines = content.split('\n')
|
||||
def is_empty_line(line):
|
||||
return len(line) != 0
|
||||
lines = filter(is_empty_line, lines)
|
||||
|
||||
# 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
|
||||
for line in split_lines:
|
||||
for index, char in enumerate(line):
|
||||
line[index] = ASCII_TO_NUMBER.get(char, 0)
|
||||
|
||||
# Return the formatted str
|
||||
return split_lines
|
||||
|
||||
ONEUP_STR = str_to_sprite("""
|
||||
|
||||
ONEUP_STR = str_to_sprite('''
|
||||
*******
|
||||
*=~~-~~=*
|
||||
*~~---~~*
|
||||
@ -128,7 +160,8 @@ ONEUP_STR = str_to_sprite("""
|
||||
**-*-*-**
|
||||
*-----*
|
||||
*****
|
||||
""")
|
||||
''')
|
||||
|
||||
|
||||
IMAGES = {
|
||||
'kitty': KITTY,
|
||||
@ -139,13 +172,15 @@ IMAGES = {
|
||||
'octocat2': OCTOCAT2,
|
||||
'hello': HELLO,
|
||||
'hireme': HIREME,
|
||||
'oneup_str':ONEUP_STR
|
||||
'oneup_str': ONEUP_STR,
|
||||
}
|
||||
|
||||
|
||||
def load_images(img_names):
|
||||
"""loads user images from given file(s)"""
|
||||
if img_names[0] == '':
|
||||
return dict()
|
||||
return {}
|
||||
|
||||
for image_name in img_names:
|
||||
img = open(image_name)
|
||||
loaded_imgs = {}
|
||||
@ -158,85 +193,108 @@ def load_images(img_names):
|
||||
img_line = img.readline()
|
||||
if img_line == '':
|
||||
break
|
||||
|
||||
img_line.replace('\n', '')
|
||||
if(img_line[0] == ':'):
|
||||
if img_line[0] == ':':
|
||||
loaded_imgs[name] = json.loads(img_list)
|
||||
name = img_line[1:]
|
||||
img_list = ''
|
||||
else:
|
||||
img_list += img_line
|
||||
|
||||
loaded_imgs[name] = json.loads(img_list)
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
url = base_url + '/contributions'
|
||||
page = urllib2.urlopen(url)
|
||||
except (urllib2.HTTPError,urllib2.URLError) as e:
|
||||
print ("There was a problem fetching data from {0}".format(url))
|
||||
page = urlopen(url)
|
||||
except (HTTPError, URLError) as e:
|
||||
print('There was a problem fetching data from {0}'.format(url))
|
||||
print(e)
|
||||
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"""
|
||||
output = set()
|
||||
for line in input:
|
||||
|
||||
for line in calendar:
|
||||
for day in line.split():
|
||||
if "data-count=" in day:
|
||||
if 'data-count=' in day:
|
||||
commit = day.split('=')[1]
|
||||
commit = commit.strip('"')
|
||||
output.add(int(commit))
|
||||
|
||||
output = list(output)
|
||||
output.sort()
|
||||
output.reverse()
|
||||
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
|
||||
if m == 0: return 1
|
||||
|
||||
if m == 0:
|
||||
return 1
|
||||
|
||||
m = math.ceil(m)
|
||||
m = int(m)
|
||||
return m
|
||||
|
||||
|
||||
def get_start_date():
|
||||
"""returns a datetime object for the first sunday after one year ago today
|
||||
at 12:00 noon"""
|
||||
d = datetime.datetime.today()
|
||||
date = datetime.datetime(d.year-1, d.month, d.day, 12)
|
||||
weekday = datetime.datetime.weekday(date)
|
||||
today = datetime.today()
|
||||
date = datetime(today.year - 1, today.month, today.day, 12)
|
||||
weekday = datetime.weekday(date)
|
||||
|
||||
while weekday < 6:
|
||||
date = date + datetime.timedelta(1)
|
||||
weekday = datetime.datetime.weekday(date)
|
||||
date = date + timedelta(1)
|
||||
weekday = datetime.weekday(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
|
||||
input. The offset is in weeks"""
|
||||
start = offset * 7
|
||||
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
|
||||
width = len(image[0])
|
||||
|
||||
for w in range(width):
|
||||
for h in range(height):
|
||||
yield image[h][w] * multiplier
|
||||
|
||||
|
||||
def commit(content, commitdate):
|
||||
template = ("""echo {0} >> gitfiti\n"""
|
||||
"""GIT_AUTHOR_DATE={1} GIT_COMMITTER_DATE={2} """
|
||||
"""git commit -a -m "gitfiti" > /dev/null\n""")
|
||||
template = (
|
||||
'''echo {0} >> gitfiti\n'''
|
||||
'''GIT_AUTHOR_DATE={1} GIT_COMMITTER_DATE={2} '''
|
||||
'''git commit -a -m "gitfiti" > /dev/null\n'''
|
||||
)
|
||||
return template.format(content, commitdate.isoformat(),
|
||||
commitdate.isoformat())
|
||||
|
||||
def fake_it(image, start_date, username, repo, offset=0, multiplier=1,
|
||||
git_url='git@github.com'):
|
||||
template = ('#!/bin/bash\n'
|
||||
|
||||
def fake_it(image, start_date, username, repo, git_url, offset=0, multiplier=1):
|
||||
template = (
|
||||
'#!/bin/bash\n'
|
||||
'REPO={0}\n'
|
||||
'git init $REPO\n'
|
||||
'cd $REPO\n'
|
||||
@ -247,84 +305,102 @@ def fake_it(image, start_date, username, repo, offset=0, multiplier=1,
|
||||
'{1}\n'
|
||||
'git remote add origin {2}:{3}/$REPO.git\n'
|
||||
'git pull\n'
|
||||
'git push -u origin master\n')
|
||||
'git push -u origin master\n'
|
||||
)
|
||||
|
||||
strings = []
|
||||
for value, date in zip(values_in_date_order(image, multiplier),
|
||||
date_gen(start_date, offset)):
|
||||
for value, date in zip(generate_values_in_date_order(image, multiplier),
|
||||
generate_next_dates(start_date, offset)):
|
||||
for i in range(value):
|
||||
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):
|
||||
"""Saves the list to a given filename"""
|
||||
f = open(filename, "w")
|
||||
with open(filename, 'w') as f:
|
||||
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():
|
||||
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:')
|
||||
repo = raw_input(">")
|
||||
ghe = request_user_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):')
|
||||
offset = raw_input(">")
|
||||
if not offset.strip():
|
||||
offset = 0
|
||||
else:
|
||||
offset = int(offset)
|
||||
username = request_user_input('Enter your GitHub username: ')
|
||||
|
||||
print ('By default gitfiti.py matches the darkest pixel to the highest\n'
|
||||
'number of commits found in your github commit/activity calendar,\n'
|
||||
git_base = ghe if ghe else GITHUB_BASE_URL
|
||||
|
||||
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'
|
||||
'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
|
||||
).format(max_commits))
|
||||
match = request_user_input()
|
||||
|
||||
print ('enter file(s) to load images from (blank if not applicable)')
|
||||
img_names = raw_input(">").split(' ')
|
||||
images = dict(IMAGES, **load_images(img_names))
|
||||
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:
|
||||
image = IMAGES['kitty']
|
||||
image = IMAGES[image_name_fallback]
|
||||
else:
|
||||
try:
|
||||
image = images[image]
|
||||
except:
|
||||
image = IMAGES['kitty']
|
||||
image = IMAGES[image_name_fallback]
|
||||
|
||||
start_date = get_start_date()
|
||||
fake_it_multiplier = m * match
|
||||
|
||||
if not ghe:
|
||||
output = fake_it(image, get_start_date(), username, repo, offset,
|
||||
m*match)
|
||||
git_url = 'git@github.com'
|
||||
else:
|
||||
git_url = raw_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)
|
||||
git_url = request_user_input('Enter Git URL like git@site.github.com: ')
|
||||
|
||||
output = fake_it(image, start_date, username, repo, git_url, offset,
|
||||
fake_it_multiplier)
|
||||
|
||||
save(output, 'gitfiti.sh')
|
||||
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__':
|
||||
main()
|
||||
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
27
tests/test_str_to_sprite.py
Normal file
27
tests/test_str_to_sprite.py
Normal 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
|
Reference in New Issue
Block a user