├── requirements.txt ├── requirements3-dev.txt ├── requirements2.7-dev.txt ├── .gitignore ├── templates ├── batman.png ├── kitty.png ├── oneup.png ├── default.png ├── oneup.tpl ├── batman.tpl ├── kitty.tpl └── default.tpl ├── .travis └── libgit2.sh ├── test ├── test_end_to_end.py ├── test_template_size.py ├── __init__.py ├── test_main.py ├── test_min_max_for_user.py ├── test_template_to_tape.py ├── test_align_template.py ├── test_load_template.py ├── test_board_origin.py └── test_sunday_offset.py ├── .travis.yml ├── README.md ├── LICENSE.md └── github_board.py /requirements.txt: -------------------------------------------------------------------------------- 1 | pygit2==0.20.2 2 | -------------------------------------------------------------------------------- /requirements3-dev.txt: -------------------------------------------------------------------------------- 1 | nose2==0.4.7 2 | -------------------------------------------------------------------------------- /requirements2.7-dev.txt: -------------------------------------------------------------------------------- 1 | mock==1.0.1 2 | nose2==0.4.7 3 | unittest2==0.5.1 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python ### 2 | *.py[cod] 3 | 4 | ### Tests ### 5 | tests/repo 6 | -------------------------------------------------------------------------------- /templates/batman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayandin/github-board/HEAD/templates/batman.png -------------------------------------------------------------------------------- /templates/kitty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayandin/github-board/HEAD/templates/kitty.png -------------------------------------------------------------------------------- /templates/oneup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayandin/github-board/HEAD/templates/oneup.png -------------------------------------------------------------------------------- /templates/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayandin/github-board/HEAD/templates/default.png -------------------------------------------------------------------------------- /templates/oneup.tpl: -------------------------------------------------------------------------------- 1 | 04444444 2 | 432212234 3 | 422111224 4 | 434444434 5 | 441414144 6 | 04111114 7 | 0044444 -------------------------------------------------------------------------------- /templates/batman.tpl: -------------------------------------------------------------------------------- 1 | 444400404004444 2 | 4444044404444 3 | 44444444444 4 | 44444444444 5 | 444444444 6 | 444 7 | 4 -------------------------------------------------------------------------------- /templates/kitty.tpl: -------------------------------------------------------------------------------- 1 | 000300003 2 | 0031333313 3 | 0031111113 4 | 113131131311 5 | 0031122113 6 | 113111111311 7 | 000233332 -------------------------------------------------------------------------------- /templates/default.tpl: -------------------------------------------------------------------------------- 1 | 01330130000130130000130000013000000000000000013 2 | 13000000130130130000130000013000000000000000013 3 | 13133131333133331313133300013331333133313331333 4 | 13013130130130131313131313313131313133313001313 5 | 01330130133130131333133300013331333131313001333 -------------------------------------------------------------------------------- /.travis/libgit2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | LIBGIT2_VERSION="0.20.0" 4 | 5 | cd ~/ 6 | wget https://github.com/libgit2/libgit2/archive/v${LIBGIT2_VERSION}.tar.gz 7 | tar -xf v${LIBGIT2_VERSION}.tar.gz 8 | mv libgit2{-${LIBGIT2_VERSION},} 9 | 10 | cd libgit2 11 | mkdir build && cd $_ 12 | cmake .. -DCMAKE_INSTALL_PREFIX=../_install -DBUILD_CLAR=OFF 13 | cmake --build . --target install 14 | -------------------------------------------------------------------------------- /test/test_end_to_end.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | from nose2.tools import params 4 | 5 | from test import RepoTestCase 6 | 7 | 8 | class TestEndToEnd(RepoTestCase): 9 | @params( 10 | ("",), 11 | ("center",), 12 | ) 13 | def test(self, alignment): 14 | return_code = subprocess.call([ 15 | "./github_board.py", 16 | "-r", self.repo.path, 17 | "-t", "./templates/default.tpl", 18 | "-e", "test@test", 19 | "-a", alignment, 20 | ]) 21 | self.assertEqual(0, return_code) 22 | -------------------------------------------------------------------------------- /test/test_template_size.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import params 2 | 3 | from test import GithubBoardTestCase 4 | 5 | from github_board import template_size 6 | 7 | 8 | class TestTemplateSize(GithubBoardTestCase): 9 | @params( 10 | ([], {"height": 0, "width": 0}), 11 | ([[1]], {"height": 1, "width": 1}), 12 | ([[1, 2]], {"height": 1, "width": 2}), 13 | ([[1], [2]], {"height": 2, "width": 1}), 14 | ([[1, 2], [3, 4, 7], [6, 7], [8]], {"height": 4, "width": 3}), 15 | ) 16 | def test(self, template, expected_result): 17 | self.assertDictEqual(expected_result, template_size(template)) 18 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | 3 | import pygit2 4 | 5 | try: 6 | import unittest2 as unittest 7 | except ImportError: 8 | import unittest 9 | 10 | try: 11 | import mock 12 | except ImportError: 13 | import unittest.mock as mock 14 | 15 | try: 16 | from StringIO import StringIO as BytesIO 17 | except ImportError: 18 | from io import BytesIO 19 | 20 | 21 | class GithubBoardTestCase(unittest.TestCase): 22 | pass 23 | 24 | 25 | class RepoTestCase(GithubBoardTestCase): 26 | def setUp(self): 27 | self.repo = pygit2.init_repository("./test/repo") 28 | 29 | def tearDown(self): 30 | shutil.rmtree(self.repo.path) 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "pypy" 5 | - "3.2" 6 | - "3.3" 7 | 8 | env: 9 | - LIBGIT2=~/libgit2/_install/ LD_LIBRARY_PATH=~/libgit2/_install/lib 10 | 11 | before_install: 12 | - .travis/libgit2.sh 13 | 14 | install: 15 | - pip install --use-mirrors -r requirements.txt 16 | - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors -r requirements2.7-dev.txt; fi 17 | - if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then pip install --use-mirrors -r requirements2.7-dev.txt; fi 18 | - if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then pip install --use-mirrors -r requirements3-dev.txt; fi 19 | 20 | script: 21 | - nose2 22 | -------------------------------------------------------------------------------- /test/test_main.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import params 2 | 3 | from test import mock 4 | from test import RepoTestCase 5 | 6 | from github_board import main 7 | 8 | 9 | class TestMain(RepoTestCase): 10 | @params( 11 | ("0",), 12 | ("1\n",), 13 | ("4",), 14 | ("123\n405670\n80",), 15 | ) 16 | def test(self, template): 17 | with mock.patch("github_board.open", mock.mock_open(read_data=template), create=True): 18 | with mock.patch("github_board.pygit2.Repository.create_commit", mock.Mock()) as m: 19 | main("test@test", self.repo.path, template, None) 20 | self.assertEquals(sum([int(i) for i in template if i.isdigit()]), len(m.mock_calls)) 21 | -------------------------------------------------------------------------------- /test/test_min_max_for_user.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import params 2 | 3 | from test import BytesIO 4 | from test import GithubBoardTestCase 5 | from test import mock 6 | 7 | from github_board import min_max_for_user 8 | 9 | 10 | class TestMinMaxForUser(GithubBoardTestCase): 11 | @params( 12 | (b'[["2013/10/21",0]]', (0, 0)), 13 | (b'[["2013/10/21",0],["2013/10/21",1]]', (1, 1)), 14 | (b'[["2013/10/21",2],["2013/10/21",1],["2013/10/21",0]]', (1, 2)), 15 | ) 16 | def test(self, data, expected_result): 17 | with mock.patch("github_board.url_request.urlopen", mock.Mock(return_value=BytesIO(data))): 18 | self.assertSequenceEqual(expected_result, min_max_for_user("some_github_user")) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github-board 2 | [![Build Status](https://travis-ci.org/bayandin/github-board.png)](https://travis-ci.org/bayandin/github-board) 3 | 4 | ![Default](templates/default.png) 5 | 6 | ### Usage 7 | ```bash 8 | $ ./github_board.py -r ~/my/repo -t ./templates/default.tpl -e my@email.com -a center 9 | ``` 10 | 11 | ### How to use on Windows? 12 | 13 | * Install Python 14 | * install pip like https://pip.pypa.io/en/stable/installing/#do-i-need-to-install-pip 15 | * run command "pip install pygit2" 16 | * Run program like explained in Usage 17 | 18 | 19 | ### Thanks 20 | * [Eric Romano](https://github.com/gelstudios) for new cool templates ([kitty](templates/kitty.png) and [oneup](templates/oneup.png) from [gelstudios/gitfiti](https://github.com/gelstudios/gitfiti)) 21 | -------------------------------------------------------------------------------- /test/test_template_to_tape.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import params 2 | 3 | from test import GithubBoardTestCase 4 | 5 | from github_board import STEP, template_to_tape 6 | 7 | 8 | class TestTemplateToTape(GithubBoardTestCase): 9 | @params( 10 | ([[1]], 0, [0]), 11 | ([[1, 1]], 0, [0, 7 * STEP]), 12 | ([[1, 0, 1]], 0, [0, 2 * 7 * STEP]), 13 | ([[0, 1]], 0, [7 * STEP]), 14 | ([[1], [1]], 0, [0, STEP]), 15 | ([[1], [0], [1]], 0, [0, 2 * STEP]), 16 | ([[0], [1]], 0, [STEP]), 17 | ([[1, 0], [0, 1]], 0, [0, 7 * STEP + STEP]), 18 | ([[0, 0], [0, 0]], 0, []), 19 | ([[0, 1], [1]], 42, [42 + 7 * STEP, 42 + STEP]), 20 | ([[3]], 0, [0, 0, 0]), 21 | ([[1, 2, 3]], 0, [0, 7 * STEP, 7 * STEP, 7 * 2 * STEP, 7 * 2 * STEP, 7 * 2 * STEP]), 22 | ([[1], [2], [3]], 0, [0, STEP, STEP, 2 * STEP, 2 * STEP, 2 * STEP]), 23 | ) 24 | def test(self, template, origin, expected_result): 25 | self.assertListEqual(expected_result, template_to_tape(template, origin)) 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Alexander Bayandin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/test_align_template.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import params 2 | 3 | from test import GithubBoardTestCase 4 | 5 | from github_board import align_template 6 | 7 | 8 | class TestAlignTemplate(GithubBoardTestCase): 9 | @params( 10 | ([[1]], [[0], [0], [0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]]), 11 | ([[1, 2]], [[0], [0], [0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2]]), 12 | ([[1], [2]], [ 13 | [0], 14 | [0], 15 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 16 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2], 17 | ]), 18 | ([[1], [2, 3]], [ 19 | [0], 20 | [0], 21 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 22 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3], 23 | ]), 24 | ) 25 | def test(self, template, expected_result): 26 | self.assertListEqual(expected_result, align_template(template, "center")) 27 | -------------------------------------------------------------------------------- /test/test_load_template.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import params 2 | 3 | from test import GithubBoardTestCase 4 | from test import mock 5 | 6 | from github_board import COMMIT_MULTIPLIER, load_template 7 | 8 | 9 | class TestLoadTemplate(GithubBoardTestCase): 10 | @params( 11 | ("1", [[1 * COMMIT_MULTIPLIER]]), 12 | ("01", [[0, 1 * COMMIT_MULTIPLIER]]), 13 | ("0\n1", [[0], [1 * COMMIT_MULTIPLIER]]), 14 | ("1\n0\n", [[1 * COMMIT_MULTIPLIER], [0]]), 15 | ("10\n01", [[1 * COMMIT_MULTIPLIER, 0], [0, 1 * COMMIT_MULTIPLIER]]), 16 | ("\n1", [[1 * COMMIT_MULTIPLIER]]), 17 | ("", []), 18 | ("\n", []), 19 | ("5", [[5 * COMMIT_MULTIPLIER]]), 20 | ("123", [[1 * COMMIT_MULTIPLIER, 2 * COMMIT_MULTIPLIER, 3 * COMMIT_MULTIPLIER]]), 21 | ("1\n2\n3", [[1 * COMMIT_MULTIPLIER], [2 * COMMIT_MULTIPLIER], [3 * COMMIT_MULTIPLIER]]), 22 | ("12\n3", [[1 * COMMIT_MULTIPLIER, 2 * COMMIT_MULTIPLIER], [3 * COMMIT_MULTIPLIER]]), 23 | ) 24 | def test(self, content, expected_result): 25 | with mock.patch("github_board.open", mock.mock_open(read_data=content), create=True): 26 | self.assertListEqual(expected_result, load_template("fake_path")) 27 | -------------------------------------------------------------------------------- /test/test_board_origin.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | import time 4 | 5 | from nose2.tools import params 6 | 7 | from test import GithubBoardTestCase 8 | from test import mock 9 | 10 | from github_board import board_origin, UTC_TO_PST_OFFSET 11 | 12 | 13 | class TestBoardOrigin(GithubBoardTestCase): 14 | @params( 15 | (datetime.date(1971, 1, 1), "UTC", 0, UTC_TO_PST_OFFSET), 16 | (datetime.date(1971, 1, 1), "Asia/Novosibirsk", 0, UTC_TO_PST_OFFSET), 17 | (datetime.date(1971, 1, 1), "America/New_York", 0, UTC_TO_PST_OFFSET), 18 | (datetime.date(1971, 1, 1), "PST8PDT", 0, UTC_TO_PST_OFFSET), 19 | (datetime.date(1971, 1, 1), "UTC", 42, 42 + UTC_TO_PST_OFFSET), 20 | (datetime.date(2014, 1, 22), "Europe/Moscow", 0, 1358812800 + UTC_TO_PST_OFFSET), 21 | ) 22 | def test(self, date, timezone, sunday_offset, expected_result): 23 | self.set_timezone(timezone) 24 | with mock.patch("github_board.sunday_offset", mock.Mock(return_value=sunday_offset)): 25 | self.assertEqual(expected_result, board_origin(date)) 26 | 27 | @staticmethod 28 | def set_timezone(timezone): 29 | """ 30 | Sets timezone 31 | 32 | :type timezone: str 33 | """ 34 | os.environ["TZ"] = timezone 35 | time.tzset() 36 | -------------------------------------------------------------------------------- /test/test_sunday_offset.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from nose2.tools import params 4 | 5 | from test import GithubBoardTestCase 6 | 7 | from github_board import STEP, sunday_offset 8 | 9 | 10 | class TestSundayOffset(GithubBoardTestCase): 11 | @params( 12 | (datetime.date(2014, 1, 19), 7 * STEP), # Sunday 13 | (datetime.date(2007, 12, 31), 6 * STEP), # Monday 14 | (datetime.date(2038, 1, 19), 5 * STEP), # Tuesday 15 | (datetime.date(2012, 2, 29), 4 * STEP), # Wednesday 16 | (datetime.date(1970, 1, 1), 3 * STEP), # Thursday 17 | (datetime.date(1990, 11, 30), 2 * STEP), # Friday 18 | (datetime.date(2007, 7, 28), STEP), # Saturday 19 | ) 20 | def test_directly(self, date, expected_result): 21 | self.assertEqual(expected_result, sunday_offset(date)) 22 | 23 | @params( 24 | (datetime.date(2014, 1, 19), -STEP), # Sunday 25 | (datetime.date(2007, 12, 31), -2 * STEP), # Monday 26 | (datetime.date(2038, 1, 19), -3 * STEP), # Tuesday 27 | (datetime.date(2012, 2, 29), -4 * STEP), # Wednesday 28 | (datetime.date(1970, 1, 1), -5 * STEP), # Thursday 29 | (datetime.date(1990, 11, 30), -6 * STEP), # Friday 30 | (datetime.date(2007, 7, 28), 0), # Saturday 31 | ) 32 | def test_inversely(self, date, expected_result): 33 | self.assertEqual(expected_result, sunday_offset(date, reverse=True)) 34 | -------------------------------------------------------------------------------- /github_board.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 5 | ░░░▒██░▒█░░░░▒█░▒█░░░░▒█░░░░░▒█░░░░░░░░░░░░░░░░▒█░░░ 6 | ░░▒█░░░░░░▒█░▒█░▒█░░░░▒█░░░░░▒█░░░░░░░░░░░░░░░░▒█░░░ 7 | ░░▒█▒██▒█▒███▒████▒█▒█▒███░░░▒███▒███▒███▒███▒███░░░ 8 | ░░▒█░▒█▒█░▒█░▒█░▒█▒█▒█▒█▒█▒██▒█▒█▒█▒█▒███▒█░░▒█▒█░░░ 9 | ░░░▒██░▒█░▒██▒█░▒█▒███▒███░░░▒███▒███▒█▒█▒█░░▒███░░░ 10 | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 11 | """ 12 | 13 | import argparse 14 | import datetime 15 | import json 16 | import time 17 | 18 | try: 19 | import urllib2 as url_request 20 | except ImportError: 21 | from urllib import request as url_request 22 | 23 | import pygit2 24 | 25 | STEP = 86400 # seconds in a day 26 | UTC_TO_PST_OFFSET = 28800 # seconds in 8 hours 27 | COMMIT_MULTIPLIER = 1 28 | 29 | 30 | def board_origin(today): 31 | """ 32 | Calculates point 0×0 (top left corner of the board) in seconds (in PST) 33 | 34 | :type today: datetime.date 35 | :rtype: int 36 | """ 37 | last_cell_dt = today 38 | first_cell_dt = datetime.date(last_cell_dt.year - 1, last_cell_dt.month, last_cell_dt.day) 39 | first_cell_ux = time.mktime(first_cell_dt.timetuple()) - time.timezone + UTC_TO_PST_OFFSET # localtime → PST 40 | return int(first_cell_ux) + sunday_offset(first_cell_dt) 41 | 42 | 43 | def sunday_offset(date, reverse=False): 44 | """ 45 | Calculates time from date to the next sunday or previous saturday in seconds 46 | 47 | :type date: datetime.date 48 | :type reverse: bool 49 | :rtype: int 50 | """ 51 | if not reverse: 52 | offset = (7 - (datetime.date.weekday(date) + 1) % 7) * STEP 53 | else: 54 | offset = -((datetime.date.weekday(date) + 2) % 7) * STEP 55 | return offset 56 | 57 | 58 | def template_to_tape(template, origin): 59 | """ 60 | Converts template to tape of timestamps 61 | 62 | :type template: list of list of int 63 | :type origin: int 64 | :rtype: list of int 65 | """ 66 | tape = [] 67 | (i, j) = (0, 0) 68 | for row in template: 69 | for col in row: 70 | if col > 0: 71 | tape.extend([origin + (i * 7 + j) * STEP] * col) 72 | i += 1 73 | i, j = 0, j + 1 74 | return tape 75 | 76 | 77 | def load_template(file_path): 78 | """ 79 | Loads template from file 80 | 81 | :type file_path: str 82 | :rtype: list of list of int 83 | """ 84 | template = [] 85 | f = open(file_path, "r") 86 | l = [] 87 | for c in f.read(): 88 | if c.isdigit(): 89 | l.append(int(c) * COMMIT_MULTIPLIER) 90 | elif c == "\n" and l: 91 | template.append(l) 92 | l = [] 93 | if l: 94 | template.append(l) 95 | f.close() 96 | return template 97 | 98 | 99 | def template_size(template): 100 | """ 101 | Returns template width and height 102 | 103 | :type template: list of list of int 104 | :rtype: dict 105 | """ 106 | size = { 107 | "width": max([len(row) for row in template]) if len(template) > 0 else 0, 108 | "height": len(template), 109 | } 110 | return size 111 | 112 | 113 | def align_template(template, alignment=None): 114 | """ 115 | Returns aligned template 116 | 117 | :type template: list of list of int 118 | :type alignment: str 119 | :rtype: list of list of int 120 | """ 121 | size = template_size(template) 122 | board = { 123 | "width": 51, 124 | "height": 7, 125 | } 126 | out = template[:] 127 | 128 | if alignment == "center": 129 | offset = { 130 | "width": int((board["width"] - size["width"]) / 2), 131 | "height": int((board["height"] - size["height"]) / 2), 132 | } 133 | for i in out: 134 | i[0:0] = [0] * offset["width"] 135 | for i in range(offset["height"]): 136 | out.insert(0, [0]) 137 | return out 138 | else: 139 | return out 140 | 141 | 142 | def min_max_for_user(user): 143 | """ 144 | Calculates min and max by count of commits from the github contribution calendar 145 | 146 | :type user: str 147 | :rtype: tuple 148 | """ 149 | url = "https://github.com/users/{user}/contributions_calendar_data".format(user=user) 150 | try: 151 | response = url_request.urlopen(url) 152 | except url_request.HTTPError: 153 | raise RuntimeError("Cannot receive data for '{user}'".format(user=user)) 154 | 155 | content = response.read() 156 | data = json.loads(content.decode("utf-8")) 157 | 158 | maximum = max(i for (_, i) in data) 159 | if maximum == 0: 160 | minimum = 0 161 | else: 162 | minimum = min(i for (_, i) in data if i > 0) 163 | 164 | return minimum, maximum 165 | 166 | 167 | def main(*args): 168 | """ 169 | The main program 170 | 171 | :type args: tuple 172 | """ 173 | email, repo_path, tpl_file, alignment = args 174 | 175 | tpl = load_template(tpl_file) 176 | tpl = align_template(tpl, alignment) 177 | 178 | repo = pygit2.init_repository(repo_path) 179 | 180 | if email is None: 181 | if "user.email" in repo.config: 182 | email = repo.config["user.email"] 183 | else: 184 | raise RuntimeError("You should specify email by command line parameter (--email or -e) " 185 | "or use one of the configuration files of the git") 186 | 187 | tree = repo.TreeBuilder().write() 188 | 189 | parents = [] if repo.is_empty else [str(repo.head.target)] 190 | for timestamp in template_to_tape(tpl, board_origin(datetime.date.today())): 191 | author = pygit2.Signature(name="Anonymous", email=email, time=timestamp) 192 | commit = repo.create_commit( 193 | "refs/heads/master", 194 | author, 195 | author, 196 | "", 197 | tree, 198 | parents 199 | ) 200 | parents = [str(commit)] 201 | 202 | 203 | if __name__ == "__main__": 204 | parser = argparse.ArgumentParser(description="GitHub board — …") 205 | parser.add_argument("-r", "--repo", required=True, help="path to your git repository") 206 | parser.add_argument("-e", "--email", help="your GitHub email, if not set, email from git config will be used") 207 | parser.add_argument("-t", "--template", required=True, help="path to file with template") 208 | parser.add_argument("-a", "--alignment", help="template alignment, supported variants: center") 209 | arguments = parser.parse_args() 210 | main(arguments.email, arguments.repo, arguments.template, arguments.alignment) 211 | --------------------------------------------------------------------------------