diff --git a/gitfiti.py b/gitfiti.py index 6dba15365..ac1afcdb5 100755 --- a/gitfiti.py +++ b/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,102 +42,117 @@ 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], -[0,0,4,2,2,2,2,2,2,4,0,0], -[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,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,2,2,2,2,2,4,0,0], + [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], +] ONEUP = [ -[0,4,4,4,4,4,4,4,0], -[4,3,2,2,1,2,2,3,4], -[4,2,2,1,1,1,2,2,4], -[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,4,4,4,4,4,4,4,0], + [4,3,2,2,1,2,2,3,4], + [4,2,2,1,1,1,2,2,4], + [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], +] ONEUP2 = [ -[0,0,4,4,4,4,4,4,4,0,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,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,4,4,4,4,4,4,4,0,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,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], +] HACKERSCHOOL = [ -[4,4,4,4,4,4], -[4,3,3,3,3,4], -[4,1,3,3,1,4], -[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], + [4,3,3,3,3,4], + [4,1,3,3,1,4], + [4,3,3,3,3,4], + [4,4,4,4,4,4], + [0,0,4,4,0,0], + [4,4,4,4,4,4], +] OCTOCAT = [ -[0,0,0,4,0,0,0,4,0], -[0,0,4,4,4,4,4,4,4], -[0,0,4,1,3,3,3,1,4], -[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,0,4,0,0,0,4,0], + [0,0,4,4,4,4,4,4,4], + [0,0,4,1,3,3,3,1,4], + [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], +] OCTOCAT2 = [ -[0,0,4,0,0,4,0], -[0,4,4,4,4,4,4], -[0,4,1,3,3,1,4], -[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,4,0,0,4,0], + [0,4,4,4,4,4,4], + [0,4,1,3,3,1,4], + [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], +] HELLO = [ -[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,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,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,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,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,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], +] 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], -[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], -[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,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], + [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], + [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], +] + 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,24 +160,27 @@ ONEUP_STR = str_to_sprite(""" **-*-*-** *-----* ***** -""") +''') + IMAGES = { -'kitty': KITTY, -'oneup':ONEUP, -'oneup2':ONEUP2, -'hackerschool':HACKERSCHOOL, -'octocat':OCTOCAT, -'octocat2':OCTOCAT2, -'hello':HELLO, -'hireme':HIREME, -'oneup_str':ONEUP_STR + 'kitty': KITTY, + 'oneup': ONEUP, + 'oneup2': ONEUP2, + 'hackerschool': HACKERSCHOOL, + 'octocat': OCTOCAT, + 'octocat2': OCTOCAT2, + 'hello': HELLO, + 'hireme': HIREME, + '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)) - print (e) + 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""" - m = max_commits/4.0 - if m == 0: return 1 + +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 + 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 + 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") - f.write(output) - f.close() + with open(filename, 'w') as f: + f.write(output) + + +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(TITLE) - 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' - '\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 + git_base = ghe if ghe else GITHUB_BASE_URL - print ('enter file(s) to load images from (blank if not applicable)') - img_names = raw_input(">").split(' ') - images = dict(IMAGES, **load_images(img_names)) + 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)) + 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: - 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('gitfiti.sh saved.') + print('Create a new(!) repo at {0}new and run the script'.format(git_base)) + if __name__ == '__main__': main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_str_to_sprite.py b/tests/test_str_to_sprite.py new file mode 100644 index 000000000..41cc43c10 --- /dev/null +++ b/tests/test_str_to_sprite.py @@ -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