Repository: gelstudios/gitfiti
Branch: main
Commit: 2437272939af
Files: 8
Total size: 25.3 KB
Directory structure:
gitextract_uqfzpv26/
├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── gitfiti.py
└── tests/
├── __init__.py
├── test_find_max_daily_commits.py
└── test_str_to_sprite.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.pyc
gitfiti.sh
gitfiti.ps1
================================================
FILE: .travis.yml
================================================
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.5-dev"
- "nightly"
install:
- "pip install pytest"
script:
- "py.test tests"
sudo: false
notifications:
email: false
================================================
FILE: LICENSE.txt
================================================
The MIT License (MIT)
Copyright (c) 2013 Eric Romano (@gelstudios).
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
================================================
FILE: README.md
================================================
[](https://travis-ci.org/gelstudios/gitfiti)
**gitfiti** _noun_ : Carefully crafted graffiti in a github commit history calendar.
An example of gitfiti in the wild:

`gitfiti.py` is a tool to decorate your github account's commit history calendar by (blatantly) abusing git's ability to accept commits _in the past_.
How? `gitfiti.py` generates a script (powershell or bash) that makes commits with the GIT_AUTHOR_DATE and GIT_COMMITTER_DATE environment variables set for each targeted pixel.
Since this is likely to clobber repo's history, it is highly recommend that you create a _new_ github repo when using gitfiti. Also, the generated script assumes you are using public-key authentication with git.
### Pixel Art

Included "art" from left to right: kitty, oneup, oneup2, hackerschool, octocat, octocat2
### Usage
1. Create a new github repo to store your handiwork.
2. Run `gitfiti.py` and follow the prompts for username, art selection, offset, and repo name.
For Python 3, use `python3`.
```console
$ python3 ./gitfiti.py
_ __ _____ __ _
____ _(_) /_/ __(_) /_(_)
/ __ `/ / __/ /_/ / __/ /
/ /_/ / / /_/ __/ / /_/ /
\__, /_/\__/_/ /_/\__/_/
/____/
Enter GitHub URL (leave blank to use https://github.com/):
```
For Python 2, use `python2`.
```console
$ python2 ./gitfiti.py
_ __ _____ __ _
____ _(_) /_/ __(_) /_(_)
/ __ `/ / __/ /_/ / __/ /
/ /_/ / / /_/ __/ / /_/ /
\__, /_/\__/_/ /_/\__/_/
/____/
Enter GitHub URL (leave blank to use https://github.com/):
```
3. Run the generated `gitfiti.sh` or `gitfiti.ps1` from your home directory (or any non-git tracked dir) and watch it go to work.
4. Wait... Seriously, you'll probably need to wait a day or two for the gitfiti to show in your commit graph.
### User Templates
The file format for personal templates is the following:
1. Each template starts off with a ":" and then a name (eg. ":foo")
2. Each line after that is part of a json-recognizable array.
3. The array contain values 0-4, 0 being blank and 4 being dark green.
4. To add multiple templates, just add another name tag as described in 1.
For example:
```
:center-blank
[[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,0,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1]]
```
This would output a 7 x 7 light green square with a single blank center square.
Once you have a file with templates, enter its name when prompted and the templates will be added to the list of options.
### Removal
Fortunately if you regret your gitfiti in the morning, removing it is fairly easy: delete the repo you created for your gitfiti (and wait).
### License
gitfiti is released under [The MIT license (MIT)](http://opensource.org/licenses/MIT)
---
#### Todo
- ~~Remove 'requests' dependency~~ [_thanks empathetic-alligator_](https://github.com/empathetic-alligator)
- ~~Web interface~~ See several web-based things below
- ~~Load "art" from a file~~ [_thanks empathetic-alligator_](https://github.com/empathetic-alligator)
- Load commit content from a file
- Text/alphabet option
- ~~powershell support!~~ [_thanks axzn_](https://github.com/axzn)
- ...
- Profit?
#### Notable derivatives or mentions
- [Vincent Van Git](https://github.com/jh3y/vincent-van-git) Vincent, which offers a [very slick web ui](https://vincent-van-git.netlify.app/) to generate a gitfiti script
- [github-calendar-customerizer](https://github.com/ZachSaucier/github-calendar-customizer) from ZachSaucier, another very [nice web GUI](https://codepen.io/ZachSaucier/full/PzVRBy) for generating gitfiti templates
- [git-art](https://github.com/jamesjarvis/git-art) from jamesjarvis, a work-alike web based [editor GUI](https://jamesjarvis.github.io/git-art/) that generates the script too
- [Pikesley's](https://github.com/pikesley) Pokrovsky, which offers Github History Vandalism [as a Service!](http://pokrovsky.herokuapp.com/)
- [PSVandalism](https://github.com/DenisBalan/PSVandalism) Wrapper around Pokrovsky, which makes possible vandalising Github History from Powershell
- [github-board](https://github.com/bayandin/github-board) commits gitfiti from easy templates
- [ghdecoy](https://github.com/tickelton/ghdecoy) fills the contribution graph with random data (sneaky!)
- [Gitfiti Painter](http://codepen.io/cbas/pen/vOXeKV) visual drawing tool for artists to easily create templates
- [git-draw](https://github.com/ben174/git-draw) a Chrome extension which will allow you to freely draw on your commit map(!)
- [github-jack](https://github.com/tardypad/github-jack) a pure bash version with space invaders and shining creepypasta
- [github-graffiti](https://github.com/mavrk/github-graffiti) a GUI editor with a bash script to allow custom designs on your commit map
- [Paint GitHub](https://paintgithub.com/) is the most convenient way to paint your GitHub contribution graph!
- [contribution-pixel-messages](https://github.com/abulvenz/contribution-pixel-messages) generates a date plan from an editable GUI
- Seen something else? Submit a pull request or open an issue!
================================================
FILE: gitfiti.py
================================================
#!/usr/bin/env python
#
# Copyright (c) 2013 Eric Romano (@gelstudios)
# released under The MIT license (MIT) http://opensource.org/licenses/MIT
#
"""
gitfiti
noun : Carefully crafted graffiti in a GitHub commit history calendar
"""
from datetime import datetime, timedelta
import itertools
import json
import math
import os
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 = '''
_ __ _____ __ _
____ _(_) /_/ __(_) /_(_)
/ __ `/ / __/ /_/ / __/ /
/ /_/ / / /_/ __/ / /_/ /
\__, /_/\__/_/ /_/\__/_/
/____/
'''
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],
]
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],
]
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],
]
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],
]
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],
]
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],
]
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],
]
HEART1 = [
[0,1,1,0,1,1,0],
[1,3,3,1,3,3,1],
[1,3,4,3,4,3,1],
[1,3,4,4,4,3,1],
[0,1,3,4,3,1,0],
[0,0,1,3,1,0,0],
[0,0,0,1,0,0,0],
]
HEART2 = [
[0,5,5,0,5,5,0],
[5,3,3,5,3,3,5],
[5,3,1,3,1,3,5],
[5,3,1,1,1,3,5],
[0,5,3,1,3,5,0],
[0,0,5,3,5,0,0],
[0,0,0,5,0,0,0],
]
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],
]
BEER = [
[0,0,0,0,0,0,0,3,3,3,0,0,3,3,3,0,3,3,3,0,3,3,3,0,0],
[0,0,1,1,1,1,0,3,0,0,3,0,3,0,0,0,3,0,0,0,3,0,0,3,0],
[0,2,2,2,2,2,0,3,0,0,3,0,3,0,0,0,3,0,0,0,3,0,0,3,0],
[2,0,2,2,2,2,0,3,3,3,0,0,3,3,3,0,3,3,3,0,3,3,3,0,0],
[2,0,2,2,2,2,0,3,0,0,3,0,3,0,0,0,3,0,0,0,3,0,3,0,0],
[0,2,2,2,2,2,0,3,0,0,3,0,3,0,0,0,3,0,0,0,3,0,0,3,0],
[0,0,2,2,2,2,0,3,3,3,0,0,3,3,3,0,3,3,3,0,3,0,0,3,0],
]
GLIDERS = [
[0,0,0,4,0,4,0,0,0,0,4,0,0,0],
[0,4,0,4,0,0,4,4,0,0,0,4,0,0],
[0,0,4,4,0,4,4,0,0,4,4,4,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,4,0,4,0,0,0,4,0,0,0,0,0,0],
[0,0,4,4,0,4,0,4,0,0,0,0,0,0],
[0,0,4,0,0,0,4,4,0,0,0,0,0,0],
]
HEART = [
[0,4,4,0,4,4,0],
[4,2,2,4,2,2,4],
[4,2,2,2,2,2,4],
[4,2,2,2,2,2,4],
[0,4,2,2,2,4,0],
[0,0,4,2,4,0,0],
[0,0,0,4,0,0,0],
]
HEART_SHINY = [
[0,4,4,0,4,4,0],
[4,2,0,4,2,2,4],
[4,0,2,2,2,2,4],
[4,2,2,2,2,2,4],
[0,4,2,2,2,4,0],
[0,0,4,2,4,0,0],
[0,0,0,4,0,0,0],
]
ASCII_TO_NUMBER = {
'_': 0,
'_': 1,
'~': 2,
'=': 3,
'*': 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 = [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('''
*******
*=~~-~~=*
*~~---~~*
*=*****=*
**-*-*-**
*-----*
*****
''')
IMAGES = {
'kitty': KITTY,
'oneup': ONEUP,
'oneup2': ONEUP2,
'hackerschool': HACKERSCHOOL,
'octocat': OCTOCAT,
'octocat2': OCTOCAT2,
'hello': HELLO,
'heart1': HEART1,
'heart2': HEART2,
'hireme': HIREME,
'oneup_str': ONEUP_STR,
'beer': BEER,
'gliders': GLIDERS,
'heart' : HEART,
'heart_shiny' : HEART_SHINY,
}
SHELLS = {
'bash': 'sh',
'powershell': 'ps1',
}
def load_images(img_names):
"""loads user images from given file(s)"""
if img_names[0] == '':
return {}
for image_name in img_names:
with open(image_name) as img:
loaded_imgs = {}
img_list = ''
img_line = ' '
name = img.readline().replace('\n', '')
name = name[1:]
while True:
img_line = img.readline()
if img_line == '':
break
img_line.replace('\n', '')
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 retrieve_contributions_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 = urlopen(url)
except (HTTPError, URLError) as e:
print('There was a problem fetching data from {0}'.format(url))
print(e)
raise SystemExit
return page.read().decode('utf-8')
def parse_contributions_calendar(contributions_calendar):
"""Yield daily counts extracted from the embedded contributions SVG."""
for line in contributions_calendar.splitlines():
# a valid line looks like this:
# <rect width="11" height="11" x="-31" y="0" class="ContributionCalendar-day" data-date="2023-02-26" data-level="3" rx="2" ry="2">23 contributions on Sunday, February 26, 2023</rect>
if 'data-date=' in line:
commit = line.split('>')[1].split()[0] # yuck
if commit.isnumeric():
yield int(commit)
def find_max_daily_commits(contributions_calendar):
"""finds the highest number of commits in one day"""
daily_counts = parse_contributions_calendar(contributions_calendar)
return max(daily_counts, default=0)
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"""
today = datetime.today()
date = datetime(today.year - 1, today.month, today.day, 12)
weekday = datetime.weekday(date)
while weekday < 6:
date = date + timedelta(1)
weekday = datetime.weekday(date)
return date
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 + timedelta(i)
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(commitdate, shell):
template_bash = (
'''GIT_AUTHOR_DATE={0} GIT_COMMITTER_DATE={1} '''
'''git commit --allow-empty -m "gitfiti" > /dev/null\n'''
)
template_powershell = (
'''$Env:GIT_AUTHOR_DATE="{0}"\n$Env:GIT_COMMITTER_DATE="{1}"\n'''
'''git commit --allow-empty -m "gitfiti" | Out-Null\n'''
)
template = template_bash if shell == 'bash' else template_powershell
return template.format(commitdate.isoformat(), commitdate.isoformat())
def fake_it(image, start_date, username, repo, git_url, shell, offset=0, multiplier=1):
template_bash = (
'#!/usr/bin/env bash\n'
'REPO={0}\n'
'git init $REPO\n'
'cd $REPO\n'
'touch README.md\n'
'git add README.md\n'
'touch gitfiti\n'
'git add gitfiti\n'
'{1}\n'
'git branch -M main\n'
'git remote add origin {2}:{3}/$REPO.git\n'
'git pull origin main\n'
'git push -u origin main\n'
)
template_powershell = (
'cd $PSScriptRoot\n'
'$REPO="{0}"\n'
'git init $REPO\n'
'cd $REPO\n'
'New-Item README.md -ItemType file | Out-Null\n'
'git add README.md\n'
'New-Item gitfiti -ItemType file | Out-Null\n'
'git add gitfiti\n'
'{1}\n'
'git branch -M main\n'
'git remote add origin {2}:{3}/$REPO.git\n'
'git pull origin main\n'
'git push -u origin main\n'
)
template = template_bash if shell == 'bash' else template_powershell
strings = []
for value, date in zip(generate_values_in_date_order(image, multiplier),
generate_next_dates(start_date, offset)):
for _ in range(value):
strings.append(commit(date, shell))
return template.format(repo, ''.join(strings), git_url, username)
def save(output, filename):
"""Saves the list to a given filename"""
with open(filename, 'w') as f:
f.write(output)
os.chmod(filename, 0o755) # add execute permissions
def request_user_input(prompt='> '):
"""Request input from the user and return what has been entered."""
return raw_input(prompt)
def main():
print(TITLE)
ghe = request_user_input(
'Enter GitHub URL (leave blank to use {}): '.format(GITHUB_BASE_URL))
username = request_user_input('Enter your GitHub username: ')
git_base = ghe if ghe else GITHUB_BASE_URL
contributions_calendar = retrieve_contributions_calendar(username, git_base)
max_daily_commits = find_max_daily_commits(contributions_calendar)
m = calculate_multiplier(max_daily_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_daily_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
if not image:
image = IMAGES[image_name_fallback]
else:
try:
image = images[image]
except:
image = IMAGES[image_name_fallback]
start_date = get_start_date()
fake_it_multiplier = m * match
if not ghe:
git_url = 'git@github.com'
else:
git_url = request_user_input('Enter Git URL like git@site.github.com: ')
shell = ''
while shell not in SHELLS.keys():
shell = request_user_input(
'Enter the target shell ({}): '.format(' or '.join(SHELLS.keys())))
output = fake_it(image, start_date, username, repo, git_url, shell, offset,
fake_it_multiplier)
output_filename = 'gitfiti.{}'.format(SHELLS[shell])
save(output, output_filename)
print('{} saved.'.format(output_filename))
print('Create a new(!) repo named {0} at {1} and run the script'.format(repo, git_base))
if __name__ == '__main__':
main()
================================================
FILE: tests/__init__.py
================================================
================================================
FILE: tests/test_find_max_daily_commits.py
================================================
from gitfiti import find_max_daily_commits, parse_contributions_calendar
CONTRIBUTIONS_CALENDAR_SVG = '''\
<svg width="721" height="110" class="js-calendar-graph-svg">
<g transform="translate(20, 20)">
<g transform="translate(624, 0)">
<rect class="day" width="11" height="11" y="0" fill="#eeeeee" data-count="0" data-date="2016-05-08"/>
<rect class="day" width="11" height="11" y="13" fill="#eeeeee" data-count="0" data-date="2016-05-09"/>
<rect class="day" width="11" height="11" y="26" fill="#eeeeee" data-count="0" data-date="2016-05-10"/>
<rect class="day" width="11" height="11" y="39" fill="#eeeeee" data-count="0" data-date="2016-05-11"/>
<rect class="day" width="11" height="11" y="52" fill="#d6e685" data-count="6" data-date="2016-05-12"/>
<rect class="day" width="11" height="11" y="65" fill="#eeeeee" data-count="0" data-date="2016-05-13"/>
<rect class="day" width="11" height="11" y="78" fill="#eeeeee" data-count="0" data-date="2016-05-14"/>
</g>
<g transform="translate(637, 0)">
<rect class="day" width="11" height="11" y="0" fill="#eeeeee" data-count="0" data-date="2016-05-15"/>
<rect class="day" width="11" height="11" y="13" fill="#eeeeee" data-count="0" data-date="2016-05-16"/>
<rect class="day" width="11" height="11" y="26" fill="#eeeeee" data-count="0" data-date="2016-05-17"/>
<rect class="day" width="11" height="11" y="39" fill="#eeeeee" data-count="0" data-date="2016-05-18"/>
<rect class="day" width="11" height="11" y="52" fill="#eeeeee" data-count="0" data-date="2016-05-19"/>
<rect class="day" width="11" height="11" y="65" fill="#eeeeee" data-count="0" data-date="2016-05-20"/>
<rect class="day" width="11" height="11" y="78" fill="#d6e685" data-count="6" data-date="2016-05-21"/>
</g>
<g transform="translate(650, 0)">
<rect class="day" width="11" height="11" y="0" fill="#1e6823" data-count="84" data-date="2016-05-22"/>
<rect class="day" width="11" height="11" y="13" fill="#d6e685" data-count="16" data-date="2016-05-23"/>
<rect class="day" width="11" height="11" y="26" fill="#d6e685" data-count="4" data-date="2016-05-24"/>
<rect class="day" width="11" height="11" y="39" fill="#d6e685" data-count="8" data-date="2016-05-25"/>
<rect class="day" width="11" height="11" y="52" fill="#eeeeee" data-count="0" data-date="2016-05-26"/>
<rect class="day" width="11" height="11" y="65" fill="#eeeeee" data-count="0" data-date="2016-05-27"/>
<rect class="day" width="11" height="11" y="78" fill="#eeeeee" data-count="0" data-date="2016-05-28"/>
</g>
<g transform="translate(663, 0)">
<rect class="day" width="11" height="11" y="0" fill="#eeeeee" data-count="0" data-date="2016-05-29"/>
<rect class="day" width="11" height="11" y="13" fill="#8cc665" data-count="25" data-date="2016-05-30"/>
<rect class="day" width="11" height="11" y="26" fill="#1e6823" data-count="66" data-date="2016-05-31"/>
<rect class="day" width="11" height="11" y="39" fill="#d6e685" data-count="20" data-date="2016-06-01"/>
<rect class="day" width="11" height="11" y="52" fill="#d6e685" data-count="10" data-date="2016-06-02"/>
<rect class="day" width="11" height="11" y="65" fill="#eeeeee" data-count="0" data-date="2016-06-03"/>
<rect class="day" width="11" height="11" y="78" fill="#eeeeee" data-count="0" data-date="2016-06-04"/>
</g>
<g transform="translate(676, 0)">
<rect class="day" width="11" height="11" y="0" fill="#8cc665" data-count="33" data-date="2016-06-05"/>
<rect class="day" width="11" height="11" y="13" fill="#d6e685" data-count="9" data-date="2016-06-06"/>
<rect class="day" width="11" height="11" y="26" fill="#eeeeee" data-count="0" data-date="2016-06-07"/>
<rect class="day" width="11" height="11" y="39" fill="#eeeeee" data-count="0" data-date="2016-06-08"/>
<rect class="day" width="11" height="11" y="52" fill="#d6e685" data-count="7" data-date="2016-06-09"/>
</g>
<text x="0" y="-5" class="month">Jun</text>
<text x="52" y="-5" class="month">Jul</text>
<text x="104" y="-5" class="month">Aug</text>
<text x="169" y="-5" class="month">Sep</text>
<text x="221" y="-5" class="month">Oct</text>
<text x="273" y="-5" class="month">Nov</text>
<text x="338" y="-5" class="month">Dec</text>
<text x="390" y="-5" class="month">Jan</text>
<text x="455" y="-5" class="month">Feb</text>
<text x="507" y="-5" class="month">Mar</text>
<text x="559" y="-5" class="month">Apr</text>
<text x="611" y="-5" class="month">May</text>
<text text-anchor="middle" class="wday" dx="-10" dy="9" style="display: none;">S</text>
<text text-anchor="middle" class="wday" dx="-10" dy="22">M</text>
<text text-anchor="middle" class="wday" dx="-10" dy="35" style="display: none;">T</text>
<text text-anchor="middle" class="wday" dx="-10" dy="48">W</text>
<text text-anchor="middle" class="wday" dx="-10" dy="61" style="display: none;">T</text>
<text text-anchor="middle" class="wday" dx="-10" dy="74">F</text>
<text text-anchor="middle" class="wday" dx="-10" dy="87" style="display: none;">S</text>
</g>
</svg>
'''
def test_parse_contributions_calendar():
expected = [
0, 0, 0, 0, 6, 0, 0,
0, 0, 0, 0, 0, 0, 6,
84, 16, 4, 8, 0, 0, 0,
0, 25, 66, 20, 10, 0, 0,
33, 9, 0, 0, 7,
]
actual = parse_contributions_calendar(CONTRIBUTIONS_CALENDAR_SVG)
assert list(actual) == expected
def test_find_max_daily_commits():
assert find_max_daily_commits(CONTRIBUTIONS_CALENDAR_SVG) == 84
================================================
FILE: tests/test_str_to_sprite.py
================================================
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
gitextract_uqfzpv26/
├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── gitfiti.py
└── tests/
├── __init__.py
├── test_find_max_daily_commits.py
└── test_str_to_sprite.py
SYMBOL INDEX (17 symbols across 3 files) FILE: gitfiti.py function str_to_sprite (line 196) | def str_to_sprite(content): function load_images (line 249) | def load_images(img_names): function retrieve_contributions_calendar (line 280) | def retrieve_contributions_calendar(username, base_url): function parse_contributions_calendar (line 295) | def parse_contributions_calendar(contributions_calendar): function find_max_daily_commits (line 307) | def find_max_daily_commits(contributions_calendar): function calculate_multiplier (line 313) | def calculate_multiplier(max_commits): function get_start_date (line 325) | def get_start_date(): function generate_next_dates (line 339) | def generate_next_dates(start_date, offset=0): function generate_values_in_date_order (line 347) | def generate_values_in_date_order(image, multiplier=1): function commit (line 356) | def commit(commitdate, shell): function fake_it (line 372) | def fake_it(image, start_date, username, repo, git_url, shell, offset=0,... function save (line 416) | def save(output, filename): function request_user_input (line 423) | def request_user_input(prompt='> '): function main (line 428) | def main(): FILE: tests/test_find_max_daily_commits.py function test_parse_contributions_calendar (line 74) | def test_parse_contributions_calendar(): function test_find_max_daily_commits (line 88) | def test_find_max_daily_commits(): FILE: tests/test_str_to_sprite.py function test_symbols_to_numbers (line 25) | def test_symbols_to_numbers():
Condensed preview — 8 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (28K chars).
[
{
"path": ".gitignore",
"chars": 29,
"preview": "*.pyc\ngitfiti.sh\ngitfiti.ps1\n"
},
{
"path": ".travis.yml",
"chars": 187,
"preview": "language: python\npython:\n - \"2.7\"\n - \"3.4\"\n - \"3.5\"\n - \"3.5-dev\"\n - \"nightly\"\ninstall:\n - \"pip install pytest\"\nscr"
},
{
"path": "LICENSE.txt",
"chars": 1091,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2013 Eric Romano (@gelstudios).\n\nPermission is hereby granted, free of charge, to a"
},
{
"path": "README.md",
"chars": 5412,
"preview": "[](https://travis-ci.org/gelstudios/gitfiti)\n"
},
{
"path": "gitfiti.py",
"chars": 12927,
"preview": "#!/usr/bin/env python\n#\n# Copyright (c) 2013 Eric Romano (@gelstudios)\n# released under The MIT license (MIT) http://ope"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_find_max_daily_commits.py",
"chars": 5837,
"preview": "from gitfiti import find_max_daily_commits, parse_contributions_calendar\n\n\nCONTRIBUTIONS_CALENDAR_SVG = '''\\\n<svg width="
},
{
"path": "tests/test_str_to_sprite.py",
"chars": 409,
"preview": "from gitfiti import str_to_sprite, ONEUP_STR\n\n\nSYMBOLS = '''\n ******* \n*=~~-~~=*\n*~~---~~*\n*=*****=*\n**-*-*-**\n *-----* "
}
]
About this extraction
This page contains the full source code of the gelstudios/gitfiti GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 8 files (25.3 KB), approximately 9.4k tokens, and a symbol index with 17 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.