├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── gitfiti-screenshot.png
├── gitfiti.py
├── pixels-large.png
├── pixels.png
└── tests
├── __init__.py
├── test_find_max_daily_commits.py
└── test_str_to_sprite.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | gitfiti.sh
3 | gitfiti.ps1
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 | - "3.4"
5 | - "3.5"
6 | - "3.5-dev"
7 | - "nightly"
8 | install:
9 | - "pip install pytest"
10 | script:
11 | - "py.test tests"
12 | sudo: false
13 | notifications:
14 | email: false
15 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Eric Romano (@gelstudios).
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/gelstudios/gitfiti)
2 |
3 | **gitfiti** _noun_ : Carefully crafted graffiti in a github commit history calendar.
4 |
5 | An example of gitfiti in the wild:
6 | 
7 |
8 | `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_.
9 |
10 | 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.
11 |
12 | 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.
13 |
14 | ### Pixel Art
15 |
16 | 
17 | Included "art" from left to right: kitty, oneup, oneup2, hackerschool, octocat, octocat2
18 |
19 | ### Usage
20 |
21 | 1. Create a new github repo to store your handiwork.
22 | 2. Run `gitfiti.py` and follow the prompts for username, art selection, offset, and repo name.
23 |
24 | For Python 3, use `python3`.
25 |
26 | ```console
27 | $ python3 ./gitfiti.py
28 |
29 | _ __ _____ __ _
30 | ____ _(_) /_/ __(_) /_(_)
31 | / __ `/ / __/ /_/ / __/ /
32 | / /_/ / / /_/ __/ / /_/ /
33 | \__, /_/\__/_/ /_/\__/_/
34 | /____/
35 |
36 | Enter GitHub URL (leave blank to use https://github.com/):
37 | ```
38 |
39 | For Python 2, use `python2`.
40 |
41 | ```console
42 | $ python2 ./gitfiti.py
43 |
44 | _ __ _____ __ _
45 | ____ _(_) /_/ __(_) /_(_)
46 | / __ `/ / __/ /_/ / __/ /
47 | / /_/ / / /_/ __/ / /_/ /
48 | \__, /_/\__/_/ /_/\__/_/
49 | /____/
50 |
51 | Enter GitHub URL (leave blank to use https://github.com/):
52 | ```
53 |
54 | 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.
55 | 4. Wait... Seriously, you'll probably need to wait a day or two for the gitfiti to show in your commit graph.
56 |
57 | ### User Templates
58 |
59 | The file format for personal templates is the following:
60 |
61 | 1. Each template starts off with a ":" and then a name (eg. ":foo")
62 | 2. Each line after that is part of a json-recognizable array.
63 | 3. The array contain values 0-4, 0 being blank and 4 being dark green.
64 | 4. To add multiple templates, just add another name tag as described in 1.
65 |
66 | For example:
67 |
68 | ```
69 | :center-blank
70 | [[1,1,1,1,1,1,1],
71 | [1,1,1,1,1,1,1],
72 | [1,1,1,1,1,1,1],
73 | [1,1,1,0,1,1,1],
74 | [1,1,1,1,1,1,1],
75 | [1,1,1,1,1,1,1],
76 | [1,1,1,1,1,1,1]]
77 | ```
78 |
79 | This would output a 7 x 7 light green square with a single blank center square.
80 |
81 | Once you have a file with templates, enter its name when prompted and the templates will be added to the list of options.
82 |
83 | ### Removal
84 |
85 | Fortunately if you regret your gitfiti in the morning, removing it is fairly easy: delete the repo you created for your gitfiti (and wait).
86 |
87 | ### License
88 |
89 | gitfiti is released under [The MIT license (MIT)](http://opensource.org/licenses/MIT)
90 |
91 | ---
92 |
93 | #### Todo
94 |
95 | - ~~Remove 'requests' dependency~~ [_thanks empathetic-alligator_](https://github.com/empathetic-alligator)
96 | - ~~Web interface~~ See several web-based things below
97 | - ~~Load "art" from a file~~ [_thanks empathetic-alligator_](https://github.com/empathetic-alligator)
98 | - Load commit content from a file
99 | - Text/alphabet option
100 | - ~~powershell support!~~ [_thanks axzn_](https://github.com/axzn)
101 | - ...
102 | - Profit?
103 |
104 | #### Notable derivatives or mentions
105 |
106 | - [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
107 | - [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
108 | - [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
109 | - [Pikesley's](https://github.com/pikesley) Pokrovsky, which offers Github History Vandalism [as a Service!](http://pokrovsky.herokuapp.com/)
110 | - [PSVandalism](https://github.com/DenisBalan/PSVandalism) Wrapper around Pokrovsky, which makes possible vandalising Github History from Powershell
111 | - [github-board](https://github.com/bayandin/github-board) commits gitfiti from easy templates
112 | - [ghdecoy](https://github.com/tickelton/ghdecoy) fills the contribution graph with random data (sneaky!)
113 | - [Gitfiti Painter](http://codepen.io/cbas/pen/vOXeKV) visual drawing tool for artists to easily create templates
114 | - [git-draw](https://github.com/ben174/git-draw) a Chrome extension which will allow you to freely draw on your commit map(!)
115 | - [github-jack](https://github.com/tardypad/github-jack) a pure bash version with space invaders and shining creepypasta
116 | - [github-graffiti](https://github.com/mavrk/github-graffiti) a GUI editor with a bash script to allow custom designs on your commit map
117 | - [Paint GitHub](https://paintgithub.com/) is the most convenient way to paint your GitHub contribution graph!
118 | - [contribution-pixel-messages](https://github.com/abulvenz/contribution-pixel-messages) generates a date plan from an editable GUI
119 | - Seen something else? Submit a pull request or open an issue!
120 |
--------------------------------------------------------------------------------
/gitfiti-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gelstudios/gitfiti/2437272939af339246f7f1330b4c016f1c8cb1f1/gitfiti-screenshot.png
--------------------------------------------------------------------------------
/gitfiti.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright (c) 2013 Eric Romano (@gelstudios)
4 | # released under The MIT license (MIT) http://opensource.org/licenses/MIT
5 | #
6 | """
7 | gitfiti
8 |
9 | noun : Carefully crafted graffiti in a GitHub commit history calendar
10 | """
11 |
12 | from datetime import datetime, timedelta
13 | import itertools
14 | import json
15 | import math
16 | import os
17 | try:
18 | # Python 3+
19 | from urllib.error import HTTPError, URLError
20 | from urllib.request import urlopen
21 | except ImportError:
22 | # Python 2
23 | from urllib2 import HTTPError, URLError, urlopen
24 |
25 | try:
26 | # Python 2
27 | raw_input
28 | except NameError:
29 | # Python 3 (Python 2's `raw_input` was renamed to `input`)
30 | raw_input = input
31 |
32 |
33 | GITHUB_BASE_URL = 'https://github.com/'
34 | FALLBACK_IMAGE = 'kitty'
35 |
36 |
37 | TITLE = '''
38 | _ __ _____ __ _
39 | ____ _(_) /_/ __(_) /_(_)
40 | / __ `/ / __/ /_/ / __/ /
41 | / /_/ / / /_/ __/ / /_/ /
42 | \__, /_/\__/_/ /_/\__/_/
43 | /____/
44 | '''
45 |
46 |
47 | KITTY = [
48 | [0,0,0,4,0,0,0,0,4,0,0,0],
49 | [0,0,4,2,4,4,4,4,2,4,0,0],
50 | [0,0,4,2,2,2,2,2,2,4,0,0],
51 | [2,2,4,2,4,2,2,4,2,4,2,2],
52 | [0,0,4,2,2,3,3,2,2,4,0,0],
53 | [2,2,4,2,2,2,2,2,2,4,2,2],
54 | [0,0,0,3,4,4,4,4,3,0,0,0],
55 | ]
56 |
57 | ONEUP = [
58 | [0,4,4,4,4,4,4,4,0],
59 | [4,3,2,2,1,2,2,3,4],
60 | [4,2,2,1,1,1,2,2,4],
61 | [4,3,4,4,4,4,4,3,4],
62 | [4,4,1,4,1,4,1,4,4],
63 | [0,4,1,1,1,1,1,4,0],
64 | [0,0,4,4,4,4,4,0,0],
65 | ]
66 |
67 | ONEUP2 = [
68 | [0,0,4,4,4,4,4,4,4,0,0],
69 | [0,4,2,2,1,1,1,2,2,4,0],
70 | [4,3,2,2,1,1,1,2,2,3,4],
71 | [4,3,3,4,4,4,4,4,3,3,4],
72 | [0,4,4,1,4,1,4,1,4,4,0],
73 | [0,0,4,1,1,1,1,1,4,0,0],
74 | [0,0,0,4,4,4,4,4,0,0,0],
75 | ]
76 |
77 | HACKERSCHOOL = [
78 | [4,4,4,4,4,4],
79 | [4,3,3,3,3,4],
80 | [4,1,3,3,1,4],
81 | [4,3,3,3,3,4],
82 | [4,4,4,4,4,4],
83 | [0,0,4,4,0,0],
84 | [4,4,4,4,4,4],
85 | ]
86 |
87 | OCTOCAT = [
88 | [0,0,0,4,0,0,0,4,0],
89 | [0,0,4,4,4,4,4,4,4],
90 | [0,0,4,1,3,3,3,1,4],
91 | [4,0,3,4,3,3,3,4,3],
92 | [0,4,0,0,4,4,4,0,0],
93 | [0,0,4,4,4,4,4,4,4],
94 | [0,0,4,0,4,0,4,0,4],
95 | ]
96 |
97 | OCTOCAT2 = [
98 | [0,0,4,0,0,4,0],
99 | [0,4,4,4,4,4,4],
100 | [0,4,1,3,3,1,4],
101 | [0,4,4,4,4,4,4],
102 | [4,0,0,4,4,0,0],
103 | [0,4,4,4,4,4,0],
104 | [0,0,0,4,4,4,0],
105 | ]
106 |
107 | HELLO = [
108 | [0,1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,4],
109 | [0,2,0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,4],
110 | [0,3,3,3,0,2,3,3,0,3,0,3,0,1,3,1,0,3],
111 | [0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,3],
112 | [0,3,0,3,0,3,3,3,0,3,0,3,0,3,0,3,0,2],
113 | [0,2,0,2,0,2,0,0,0,2,0,2,0,2,0,2,0,0],
114 | [0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,4],
115 | ]
116 |
117 | HEART1 = [
118 | [0,1,1,0,1,1,0],
119 | [1,3,3,1,3,3,1],
120 | [1,3,4,3,4,3,1],
121 | [1,3,4,4,4,3,1],
122 | [0,1,3,4,3,1,0],
123 | [0,0,1,3,1,0,0],
124 | [0,0,0,1,0,0,0],
125 | ]
126 |
127 | HEART2 = [
128 | [0,5,5,0,5,5,0],
129 | [5,3,3,5,3,3,5],
130 | [5,3,1,3,1,3,5],
131 | [5,3,1,1,1,3,5],
132 | [0,5,3,1,3,5,0],
133 | [0,0,5,3,5,0,0],
134 | [0,0,0,5,0,0,0],
135 | ]
136 |
137 | HIREME = [
138 | [1,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
139 | [2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
140 | [3,3,3,0,2,0,3,3,3,0,2,3,3,0,0,3,3,0,3,0,0,2,3,3],
141 | [4,0,4,0,4,0,4,0,0,0,4,0,4,0,0,4,0,4,0,4,0,4,0,4],
142 | [3,0,3,0,3,0,3,0,0,0,3,3,3,0,0,3,0,3,0,3,0,3,3,3],
143 | [2,0,2,0,2,0,2,0,0,0,2,0,0,0,0,2,0,2,0,2,0,2,0,0],
144 | [1,0,1,0,1,0,1,0,0,0,1,1,1,0,0,1,0,1,0,1,0,1,1,1],
145 | ]
146 |
147 | BEER = [
148 | [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],
149 | [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],
150 | [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],
151 | [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],
152 | [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],
153 | [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],
154 | [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],
155 | ]
156 |
157 | GLIDERS = [
158 | [0,0,0,4,0,4,0,0,0,0,4,0,0,0],
159 | [0,4,0,4,0,0,4,4,0,0,0,4,0,0],
160 | [0,0,4,4,0,4,4,0,0,4,4,4,0,0],
161 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0],
162 | [0,4,0,4,0,0,0,4,0,0,0,0,0,0],
163 | [0,0,4,4,0,4,0,4,0,0,0,0,0,0],
164 | [0,0,4,0,0,0,4,4,0,0,0,0,0,0],
165 | ]
166 |
167 | HEART = [
168 | [0,4,4,0,4,4,0],
169 | [4,2,2,4,2,2,4],
170 | [4,2,2,2,2,2,4],
171 | [4,2,2,2,2,2,4],
172 | [0,4,2,2,2,4,0],
173 | [0,0,4,2,4,0,0],
174 | [0,0,0,4,0,0,0],
175 | ]
176 |
177 | HEART_SHINY = [
178 | [0,4,4,0,4,4,0],
179 | [4,2,0,4,2,2,4],
180 | [4,0,2,2,2,2,4],
181 | [4,2,2,2,2,2,4],
182 | [0,4,2,2,2,4,0],
183 | [0,0,4,2,4,0,0],
184 | [0,0,0,4,0,0,0],
185 | ]
186 |
187 | ASCII_TO_NUMBER = {
188 | '_': 0,
189 | '_': 1,
190 | '~': 2,
191 | '=': 3,
192 | '*': 4,
193 | }
194 |
195 |
196 | def str_to_sprite(content):
197 | # Break out lines and filter any excess
198 | lines = content.split('\n')
199 | def is_empty_line(line):
200 | return len(line) != 0
201 | lines = filter(is_empty_line, lines)
202 |
203 | # Break up lines into each character
204 | split_lines = [list(line) for line in lines]
205 |
206 | # Replace each character with its numeric equivalent
207 | for line in split_lines:
208 | for index, char in enumerate(line):
209 | line[index] = ASCII_TO_NUMBER.get(char, 0)
210 |
211 | # Return the formatted str
212 | return split_lines
213 |
214 |
215 | ONEUP_STR = str_to_sprite('''
216 | *******
217 | *=~~-~~=*
218 | *~~---~~*
219 | *=*****=*
220 | **-*-*-**
221 | *-----*
222 | *****
223 | ''')
224 |
225 |
226 | IMAGES = {
227 | 'kitty': KITTY,
228 | 'oneup': ONEUP,
229 | 'oneup2': ONEUP2,
230 | 'hackerschool': HACKERSCHOOL,
231 | 'octocat': OCTOCAT,
232 | 'octocat2': OCTOCAT2,
233 | 'hello': HELLO,
234 | 'heart1': HEART1,
235 | 'heart2': HEART2,
236 | 'hireme': HIREME,
237 | 'oneup_str': ONEUP_STR,
238 | 'beer': BEER,
239 | 'gliders': GLIDERS,
240 | 'heart' : HEART,
241 | 'heart_shiny' : HEART_SHINY,
242 | }
243 |
244 | SHELLS = {
245 | 'bash': 'sh',
246 | 'powershell': 'ps1',
247 | }
248 |
249 | def load_images(img_names):
250 | """loads user images from given file(s)"""
251 | if img_names[0] == '':
252 | return {}
253 |
254 | for image_name in img_names:
255 | with open(image_name) as img:
256 | loaded_imgs = {}
257 | img_list = ''
258 | img_line = ' '
259 | name = img.readline().replace('\n', '')
260 | name = name[1:]
261 |
262 | while True:
263 | img_line = img.readline()
264 | if img_line == '':
265 | break
266 |
267 | img_line.replace('\n', '')
268 | if img_line[0] == ':':
269 | loaded_imgs[name] = json.loads(img_list)
270 | name = img_line[1:]
271 | img_list = ''
272 | else:
273 | img_list += img_line
274 |
275 | loaded_imgs[name] = json.loads(img_list)
276 |
277 | return loaded_imgs
278 |
279 |
280 | def retrieve_contributions_calendar(username, base_url):
281 | """retrieves the GitHub commit calendar data for a username"""
282 | base_url = base_url + 'users/' + username
283 |
284 | try:
285 | url = base_url + '/contributions'
286 | page = urlopen(url)
287 | except (HTTPError, URLError) as e:
288 | print('There was a problem fetching data from {0}'.format(url))
289 | print(e)
290 | raise SystemExit
291 |
292 | return page.read().decode('utf-8')
293 |
294 |
295 | def parse_contributions_calendar(contributions_calendar):
296 | """Yield daily counts extracted from the embedded contributions SVG."""
297 | for line in contributions_calendar.splitlines():
298 | # a valid line looks like this:
299 | # 23 contributions on Sunday, February 26, 2023
300 | if 'data-date=' in line:
301 | commit = line.split('>')[1].split()[0] # yuck
302 |
303 | if commit.isnumeric():
304 | yield int(commit)
305 |
306 |
307 | def find_max_daily_commits(contributions_calendar):
308 | """finds the highest number of commits in one day"""
309 | daily_counts = parse_contributions_calendar(contributions_calendar)
310 | return max(daily_counts, default=0)
311 |
312 |
313 | def calculate_multiplier(max_commits):
314 | """calculates a multiplier to scale GitHub colors to commit history"""
315 | m = max_commits / 4.0
316 |
317 | if m == 0:
318 | return 1
319 |
320 | m = math.ceil(m)
321 | m = int(m)
322 | return m
323 |
324 |
325 | def get_start_date():
326 | """returns a datetime object for the first sunday after one year ago today
327 | at 12:00 noon"""
328 | today = datetime.today()
329 | date = datetime(today.year - 1, today.month, today.day, 12)
330 | weekday = datetime.weekday(date)
331 |
332 | while weekday < 6:
333 | date = date + timedelta(1)
334 | weekday = datetime.weekday(date)
335 |
336 | return date
337 |
338 |
339 | def generate_next_dates(start_date, offset=0):
340 | """generator that returns the next date, requires a datetime object as
341 | input. The offset is in weeks"""
342 | start = offset * 7
343 | for i in itertools.count(start):
344 | yield start_date + timedelta(i)
345 |
346 |
347 | def generate_values_in_date_order(image, multiplier=1):
348 | height = 7
349 | width = len(image[0])
350 |
351 | for w in range(width):
352 | for h in range(height):
353 | yield image[h][w] * multiplier
354 |
355 |
356 | def commit(commitdate, shell):
357 | template_bash = (
358 | '''GIT_AUTHOR_DATE={0} GIT_COMMITTER_DATE={1} '''
359 | '''git commit --allow-empty -m "gitfiti" > /dev/null\n'''
360 | )
361 |
362 | template_powershell = (
363 | '''$Env:GIT_AUTHOR_DATE="{0}"\n$Env:GIT_COMMITTER_DATE="{1}"\n'''
364 | '''git commit --allow-empty -m "gitfiti" | Out-Null\n'''
365 | )
366 |
367 | template = template_bash if shell == 'bash' else template_powershell
368 |
369 | return template.format(commitdate.isoformat(), commitdate.isoformat())
370 |
371 |
372 | def fake_it(image, start_date, username, repo, git_url, shell, offset=0, multiplier=1):
373 | template_bash = (
374 | '#!/usr/bin/env bash\n'
375 | 'REPO={0}\n'
376 | 'git init $REPO\n'
377 | 'cd $REPO\n'
378 | 'touch README.md\n'
379 | 'git add README.md\n'
380 | 'touch gitfiti\n'
381 | 'git add gitfiti\n'
382 | '{1}\n'
383 | 'git branch -M main\n'
384 | 'git remote add origin {2}:{3}/$REPO.git\n'
385 | 'git pull origin main\n'
386 | 'git push -u origin main\n'
387 | )
388 |
389 | template_powershell = (
390 | 'cd $PSScriptRoot\n'
391 | '$REPO="{0}"\n'
392 | 'git init $REPO\n'
393 | 'cd $REPO\n'
394 | 'New-Item README.md -ItemType file | Out-Null\n'
395 | 'git add README.md\n'
396 | 'New-Item gitfiti -ItemType file | Out-Null\n'
397 | 'git add gitfiti\n'
398 | '{1}\n'
399 | 'git branch -M main\n'
400 | 'git remote add origin {2}:{3}/$REPO.git\n'
401 | 'git pull origin main\n'
402 | 'git push -u origin main\n'
403 | )
404 |
405 | template = template_bash if shell == 'bash' else template_powershell
406 |
407 | strings = []
408 | for value, date in zip(generate_values_in_date_order(image, multiplier),
409 | generate_next_dates(start_date, offset)):
410 | for _ in range(value):
411 | strings.append(commit(date, shell))
412 |
413 | return template.format(repo, ''.join(strings), git_url, username)
414 |
415 |
416 | def save(output, filename):
417 | """Saves the list to a given filename"""
418 | with open(filename, 'w') as f:
419 | f.write(output)
420 | os.chmod(filename, 0o755) # add execute permissions
421 |
422 |
423 | def request_user_input(prompt='> '):
424 | """Request input from the user and return what has been entered."""
425 | return raw_input(prompt)
426 |
427 |
428 | def main():
429 | print(TITLE)
430 |
431 | ghe = request_user_input(
432 | 'Enter GitHub URL (leave blank to use {}): '.format(GITHUB_BASE_URL))
433 |
434 | username = request_user_input('Enter your GitHub username: ')
435 |
436 | git_base = ghe if ghe else GITHUB_BASE_URL
437 |
438 | contributions_calendar = retrieve_contributions_calendar(username, git_base)
439 |
440 | max_daily_commits = find_max_daily_commits(contributions_calendar)
441 |
442 | m = calculate_multiplier(max_daily_commits)
443 |
444 | repo = request_user_input(
445 | 'Enter the name of the repository to use by gitfiti: ')
446 |
447 | offset = request_user_input(
448 | 'Enter the number of weeks to offset the image (from the left): ')
449 |
450 | offset = int(offset) if offset.strip() else 0
451 |
452 | print((
453 | 'By default gitfiti.py matches the darkest pixel to the highest\n'
454 | 'number of commits found in your GitHub commit/activity calendar,\n'
455 | '\n'
456 | 'Currently this is: {0} commits\n'
457 | '\n'
458 | 'Enter the word "gitfiti" to exceed your max\n'
459 | '(this option generates WAY more commits)\n'
460 | 'Any other input will cause the default matching behavior'
461 | ).format(max_daily_commits))
462 | match = request_user_input()
463 |
464 | match = m if (match == 'gitfiti') else 1
465 |
466 | print('Enter file(s) to load images from (blank if not applicable)')
467 | img_names = request_user_input().split(' ')
468 |
469 | loaded_images = load_images(img_names)
470 | images = dict(IMAGES, **loaded_images)
471 |
472 | print('Enter the image name to gitfiti')
473 | print('Images: ' + ', '.join(images.keys()))
474 | image = request_user_input()
475 |
476 | image_name_fallback = FALLBACK_IMAGE
477 |
478 | if not image:
479 | image = IMAGES[image_name_fallback]
480 | else:
481 | try:
482 | image = images[image]
483 | except:
484 | image = IMAGES[image_name_fallback]
485 |
486 | start_date = get_start_date()
487 | fake_it_multiplier = m * match
488 |
489 | if not ghe:
490 | git_url = 'git@github.com'
491 | else:
492 | git_url = request_user_input('Enter Git URL like git@site.github.com: ')
493 |
494 | shell = ''
495 | while shell not in SHELLS.keys():
496 | shell = request_user_input(
497 | 'Enter the target shell ({}): '.format(' or '.join(SHELLS.keys())))
498 |
499 | output = fake_it(image, start_date, username, repo, git_url, shell, offset,
500 | fake_it_multiplier)
501 |
502 | output_filename = 'gitfiti.{}'.format(SHELLS[shell])
503 | save(output, output_filename)
504 | print('{} saved.'.format(output_filename))
505 | print('Create a new(!) repo named {0} at {1} and run the script'.format(repo, git_base))
506 |
507 |
508 | if __name__ == '__main__':
509 | main()
510 |
--------------------------------------------------------------------------------
/pixels-large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gelstudios/gitfiti/2437272939af339246f7f1330b4c016f1c8cb1f1/pixels-large.png
--------------------------------------------------------------------------------
/pixels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gelstudios/gitfiti/2437272939af339246f7f1330b4c016f1c8cb1f1/pixels.png
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gelstudios/gitfiti/2437272939af339246f7f1330b4c016f1c8cb1f1/tests/__init__.py
--------------------------------------------------------------------------------
/tests/test_find_max_daily_commits.py:
--------------------------------------------------------------------------------
1 | from gitfiti import find_max_daily_commits, parse_contributions_calendar
2 |
3 |
4 | CONTRIBUTIONS_CALENDAR_SVG = '''\
5 |
71 | '''
72 |
73 |
74 | def test_parse_contributions_calendar():
75 | expected = [
76 | 0, 0, 0, 0, 6, 0, 0,
77 | 0, 0, 0, 0, 0, 0, 6,
78 | 84, 16, 4, 8, 0, 0, 0,
79 | 0, 25, 66, 20, 10, 0, 0,
80 | 33, 9, 0, 0, 7,
81 | ]
82 |
83 | actual = parse_contributions_calendar(CONTRIBUTIONS_CALENDAR_SVG)
84 |
85 | assert list(actual) == expected
86 |
87 |
88 | def test_find_max_daily_commits():
89 | assert find_max_daily_commits(CONTRIBUTIONS_CALENDAR_SVG) == 84
90 |
--------------------------------------------------------------------------------
/tests/test_str_to_sprite.py:
--------------------------------------------------------------------------------
1 | from gitfiti import str_to_sprite, ONEUP_STR
2 |
3 |
4 | SYMBOLS = '''
5 | *******
6 | *=~~-~~=*
7 | *~~---~~*
8 | *=*****=*
9 | **-*-*-**
10 | *-----*
11 | *****
12 | '''
13 |
14 | NUMBERS = [
15 | [0,4,4,4,4,4,4,4,0],
16 | [4,3,2,2,0,2,2,3,4],
17 | [4,2,2,0,0,0,2,2,4],
18 | [4,3,4,4,4,4,4,3,4],
19 | [4,4,0,4,0,4,0,4,4],
20 | [0,4,0,0,0,0,0,4,0],
21 | [0,0,4,4,4,4,4,0,0],
22 | ]
23 |
24 |
25 | def test_symbols_to_numbers():
26 | actual = str_to_sprite(SYMBOLS)
27 | assert actual == NUMBERS
28 |
--------------------------------------------------------------------------------